React Native & Expo New Architecture Integration Guide
Background
This guide provides steps for integrating a React Native Expo project with the given dependencies. Please ensure familiarity with the project’s architecture before proceeding.
Prerequisites
React Native Expo
- Expo SDK: 52.0.28+
- React: 18.2.0+
- React Native: 0.76+
iOS
- Xcode: 15.3+
- Swift: 5.9
- iOS: 15+
Android
- Android Studio: 4.0+
- Kotlin: 2.0.2+ or Java: 8+
- Minimum SDK Version: 24
Obtain an SDK API Key
An SDK API Key is required to integrate StreamLayer into your application. If you don’t already have one, follow the instructions below:
- Provide your email address to create an organization on our platform. This will include setting up a personal dashboard for your account.
- You will receive an invitation email with a link to the admin panel and your authentication credentials.
- Once you gain access to the admin panel, generate an API key in the development section. This API key will authenticate your application with our servers and is required for all interactions.
Install the necessary packages with the following command:
Only for Expo
We install
expo-screen-orientation
to control and manage screen orientation within our application. This package allows us to lock or unlock the screen orientation programmatically.
npm
npm install react-native-streamlayer-new-arch expo-screen-orientation yarn
yarn add react-native-streamlayer-new-arch expo-screen-orientation Native iOS Configuration
Update Cocoapods Dependencies
Navigate to the ios/ directory and install the dependencies:
cd ios/ && pod install && cd ../Upgrade minimum Ios Target
Update the minimum iOS version in the Podfile.
platform :ios, '15.0'Update app.json
app.jsonAdd the following plugins to your app.json file:
{
"expo": {
"name": "YourApp",
"slug": "your-app",
"version": "1.0.0",
"sdkVersion": "51.0.0",
"plugins": [
"expo-router",
[
"expo-screen-orientation",
{
"initialOrientation": "DEFAULT"
}
]
]
}
}StreamLayer SDK Setup for React Native
Add API Keys to .env
.env- Install
react-native-config:
npm install react-native-configyarn add react-native-config- In the
iosdirectory, run:
pod install- Add the following line to
android/app/build.gradle:
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"- Create a
.envfile in your project’s root directory and add your API key:
SL_SDK_API_KEY="your_api_key_here"Add imports
import {
StreamLayer,
StreamLayerView,
StreamLayerViewPlayer,
StreamLayerViewOverlayLandscapeMode,
StreamLayerViewConfiguration,
StreamLayerViewNotificationFeature,
StreamLayerDemoEvent,
StreamLayerTheme
} from 'react-native-streamlayer';
Add variables
The LBarState class represents the state of an interactive bar overlay in the StreamLayer integration. It tracks the horizontal (slideX) and vertical (slideY) positions of the overlay component. Instances of this class are used to store and update UI state when the L-bar position changes.
class LBarState {
slideX: number;
slideY: number;
constructor(slideX: number, slideY: number) {
this.slideX = slideX
this.slideY = slideY
}
}Player Height Calculation
The playerHeight variable determines the height of the player UI. If the screen is in portrait mode, a fixed height of 300px is assigned. Otherwise, the height is dynamically adjusted to match the full screen height using Dimensions.get('screen').height.
const playerHeight = isScreenPortrait() ? 300 : Dimensions.get('screen').height;StreamLayer Player Configuration
This object acts as a placeholder for the StreamLayerViewPlayer. The volume property is defined with a getter and setter but is currently set to 0.0. The setter can be extended to implement volume control when required.
const streamLayerViewPlayer: StreamLayerViewPlayer = {
get volume() {
return 0.0
},
set volume(value) {
// Custom logic for setting volume if needed
},
}View Configuration
The viewConfig object is obtained from getViewConfig(), which contains custom configurations for the StreamLayerView component. This includes settings for overlays, layout adjustments, and rendering preferences.
const viewConfig = getViewConfig()State Variables
/// Stores the player's volume before applying audio ducking, used to restore it later.
const [volumeBeforeDucking, setVolumeBeforeDucking] = useState<number | undefined>(undefined)
/// Tracks the screen orientation to adjust UI layout accordingly.
const [isPortrait, setPortrait] = useState<boolean>();
/// Holds the current state of the L-bar overlay.
const [lbarState, setLbarState] = useState(new LBarState(0, 0));
/// Stores the list of available StreamLayerDemoEvent instances.
const [events, setEvents] = useState<Array<StreamLayerDemoEvent>>()
/// Stores the ID of the currently active event.
const [currentEventId, setCurrentEventId] = useState<String>()
/// Tracks whether the StreamLayerView has been initialized
const [isInitialized, setInitialized] = useState(false);View Reference
A useRef hook is used to store a reference to the StreamLayerView. This allows direct interaction with the view without causing unnecessary re-renders.
const viewRef = useRef<StreamLayerView>(null);Generating Scrollable Event List
The scrollItems array is dynamically populated with Pressable components representing events.
var scrollItems = new Array<ReactElement>();
if (events !== undefined) {
events.forEach((event) => {
scrollItems.push(
<Pressable key={event.id} onPress={() => createEventSession(event.id)}>
<View style={styles.eventRow}>
{event.previewUrl !== undefined && (
<Image source={{ uri: event.previewUrl }}
style={styles.eventRowImage} />
)}
<Text style={styles.eventRowTitle} numberOfLines={1} ellipsizeMode='tail'>
{event.title}
</Text>
</View>
</Pressable>
);
});
}Finding the Current Event
This logic retrieves the currently selected event by matching currentEventId against the events list. If no match is found, currentEvent remains undefined.
var currentEvent: StreamLayerDemoEvent | undefined;
if (events !== undefined && currentEventId !== undefined) {
currentEvent = events.find((event) => {
return event.id == currentEventId
})
}
Add methods
Initialization and Orientation Handling
The useEffect hook is responsible for initializing the StreamLayer SDK and tracking screen orientation changes. When the component mounts, it determines if the device is in portrait mode and initializes StreamLayer. Additionally, it listens for screen dimension changes to update the portrait state dynamically. When the component unmounts, the event listener is removed to prevent memory leaks.
useEffect(() => {
await ScreenOrientation.unlockAsync(); /// Only for Expo to unlock the screen orientation
setPortrait(isScreenPortrait())
const initialize = async () => {
try {
checkInitialized();
} catch (error) {
console.error("Error initializing:", error);
}
};
initialize();
const subscription = Dimensions.addEventListener('change', ({ window, screen }) => {
setPortrait(window.height > window.width)
});
return () => subscription?.remove()
}, []);
function isScreenPortrait(): boolean {
return Dimensions.get('window').height > Dimensions.get('window').width
}
StreamLayer View Configuration
The getViewConfig function returns an object that configures the StreamLayer UI. It defines which notification features are enabled, whether points for games should be displayed, and settings for UI elements like the launch button, tooltips, and profile visibility. The overlay settings control the behavior of the expandable StreamLayer overlay.
function getViewConfig(): StreamLayerViewConfiguration {
return {
viewNotificationFeatures: new Array(
StreamLayerViewNotificationFeature.Games,
StreamLayerViewNotificationFeature.Chat,
StreamLayerViewNotificationFeature.WatchParty,
StreamLayerViewNotificationFeature.Twitter
),
isGamesPointsEnabled: true,
isGamesPointsStartSide: false,
isLaunchButtonEnabled: true,
isMenuAlwaysOpened: false,
isMenuLabelsVisible: true,
isMenuProfileEnabled: true,
isTooltipsEnabled: true,
isWatchPartyReturnButtonEnabled: true,
isWhoIsWatchingViewEnabled: true,
isOverlayExpandable: true,
overlayHeightSpace: 300,
overlayWidth: 0,
overlayLandscapeMode: StreamLayerViewOverlayLandscapeMode.Start
}
}
Player Setup
When the THEOplayer instance is ready, the onReady function sets up the player with autoplay enabled and assigns a media source. Additionally, it listens for error events to handle any issues that might occur during playback.
const onReady = (player: THEOplayer) => {
setPlayer(player);
player.autoplay = true
player.source = source;
player.addEventListener(PlayerEventType.ERROR, console.log);
}StreamLayer SDK Initialization and Authentication
The checkInitialized function initializes the StreamLayer SDK, enabling logging and applying a theme. It then verifies whether the SDK has been successfully initialized and updates the state accordingly.
const checkInitialized = async () => {
try {
await StreamLayer.initSdk({
isLoggingEnabled: true,
theme: StreamLayerTheme.Green,
sdkKey: "YOUR SDK KEY"
},false)
checkAuth()
loadDemoEvents()
const inited = await StreamLayer.isInitialized()
setInitialized(inited)
} catch (e) {
console.error(e);
}
}To ensure authentication, the checkAuth function checks if the user is authorized. If not, it triggers anonymous authentication to allow access without requiring login credentials.
const checkAuth = async () => {
try {
const isUserAuthorized = await StreamLayer.isUserAuthorized()
if (!isUserAuthorized) {
await StreamLayer.useAnonymousAuth();
}
} catch (e) {
console.error(e);
}
}The loadDemoEvents function fetches a list of demo events from the StreamLayer API based on a specific date. If events exist, the first event is used to create a session.
const loadDemoEvents = async () => {
try {
const events = await StreamLayer.getDemoEvents("2022-01-01")
setEvents(events)
if (events !== undefined && events !== null && events.length > 0) {
createEventSession(events[0].id)
}
} catch (e) {
console.error("PlayerScreen loadDemoEvents error", e);
}
}The createEventSession function creates a new session for an event using its ID, ensuring that event-based interactions are properly initialized.
const createEventSession = async (id: string) => {
try {
await StreamLayer.createEventSession(id);
console.log(`Created a new event with id ${id}`);
setCurrentEventId(id)
} catch (e) {
console.error(e);
}
};Add functions for view event listener
/// Initializes an event session for that stream.
const onRequestStream = (id: string) => {
createEventSession(id)
}
/// Updates the local state for the LBar UI component whenever its position changes.
const onLBarStateChanged = (slideX: number, slideY: number) => {
setLbarState(new LBarState(slideX, slideY));
}
/// Handles audio ducking requests from StreamLayer.
const onRequestAudioDucking = (level: number) => {
/// Add your handlers
}
/// Handles the event when audio ducking needs to be disabled.
const onDisableAudioDucking = () => {
/// Add your handlers
}Add Views for StreamLayer and StreamLayer Events
The following component sets up the main StreamLayer view within the app. It integrates StreamLayer’s event system, applying UI adjustments based on the user's interaction.
return (
<SafeAreaView style={{...styles.container, marginTop: insets.top }} edges={['top']}>
{(isPortrait) &&
/// Displays the event title and scrollable event list when the screen is in portrait mode.
<View style={{ flex: 1, marginTop: playerHeight - insets.top }}>
{currentEvent !== undefined && (
<Text style={styles.eventTitle}>{currentEvent.title}</Text>
)}
<ScrollView style={{ flex: 1 }}>
{scrollItems}
</ScrollView>
</View>
}
{isInitialized &&
/// StreamLayer overlay that takes your component and scales it down when the l-bar arrives
<StreamLayerView
style={StyleSheet.absoluteFillObject}
ref={viewRef}
config={viewConfig}
applyWindowInsets={false}
onRequestStream={onRequestStream}
onLBarStateChanged={onLBarStateChanged}
onRequestAudioDucking={onRequestAudioDucking}
onDisableAudioDucking={onDisableAudioDucking}
player={streamLayerViewPlayer}
playerView={
<View>
/// YOUR VIEW SCALES DOWN WHEN THE L-Bar ARRIVES
</View>
}
/>
}
</SafeAreaView>
)Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'gray',
},
eventTitle: {
color: 'white',
fontSize: 24,
margin: 4
},
eventRowTitle: {
flex: 1,
color: 'white',
fontSize: 16,
margin: 4
},
eventRow: {
flexDirection: 'row',
margin: 4,
padding: 4,
height: 58,
justifyContent: 'flex-start',
alignItems: 'center',
borderWidth: 1,
borderColor: 'black',
},
eventRowImage: {
width: 100,
height: 50
},
overlay: {
paddingTop: 500,
backgroundColor: '#00000000',
flex: 1,
}
});
Further Resources
- Expo Documentation: Explore Expo fundamentals and advanced guides.
- Expo Tutorial: Learn Expo with a step-by-step project tutorial.
- Expo GitHub: Contribute to the Expo open-source platform.
Join the Community
- Expo on Discord: Chat with other Expo users and developers.
Updated 7 months ago
