The Embed React Native SDK is a powerful voice-enabled AI agent library that provides real-time communication capabilities. This comprehensive guide will walk you through the complete integration process from installation to deployment.
- Installation
- Peer Dependencies
- Android Configuration
- iOS Configuration
- Babel Configuration
- SDK Initialization
- App Setup
- Event System
- Usage Examples
- FAQ & Troubleshooting
Install the Embed React Native SDK using your preferred package manager:
# Using npm
npm install @revrag-ai/embed-react-native
# Using yarn
yarn add @revrag-ai/embed-react-native
# Using pnpm
pnpm add @revrag-ai/embed-react-native
The SDK requires several peer dependencies to be installed in your project. Install all required dependencies:
# Install peer dependencies
npm install @livekit/react-native @livekit/react-native-webrtc
npm install @react-native-async-storage/async-storage
npm install react-native-gesture-handler react-native-reanimated
npm install react-native-linear-gradient lottie-react-native
npm install react-native-safe-area-context
# For iOS, run pod install
cd ios && pod install && cd ..
Using Yarn:
yarn add @livekit/react-native @livekit/react-native-webrtc @react-native-async-storage/async-storage react-native-gesture-handler react-native-reanimated react-native-linear-gradient lottie-react-native react-native-safe-area-context
Using pnpm:
pnpm add @livekit/react-native @livekit/react-native-webrtc @react-native-async-storage/async-storage react-native-gesture-handler react-native-reanimated react-native-linear-gradient lottie-react-native react-native-safe-area-context
Add the following permissions to your android/app/src/main/AndroidManifest.xml
:
<manifest xmlns:android="<http://schemas.android.com/apk/res/android>">
<!-- Required permissions for Embed SDK -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.MICROPHONE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:hardwareAccelerated="true">
<!-- Your activities and other components -->
</application>
</manifest>
Add Lottie dependency to your android/app/build.gradle
:
dependencies {
implementation 'com.airbnb.android:lottie:6.0.1'
// ... other dependencies
}
Add to your android/app/proguard-rules.pro
:
# Embed SDK
-keep class com.revrag.embed.** { *; }
-keep class org.webrtc.** { *; }
-dontwarn org.webrtc.**
# Lottie
-keep class com.airbnb.lottie.** { *; }
🚨 CRITICAL: Add the following permissions to your ios/YourAppName/Info.plist
. Missing NSMicrophoneUsageDescription
will cause the app to crash when accessing the microphone:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for voice communication with AI agent</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
NSMicrophoneUsageDescription
key is present in your Info.plist
.
After installing peer dependencies, run:
cd ios
pod install
cd ..
If you encounter build issues, add these to your iOS project settings:
- Enable Bitcode:
NO
- Build Active Architecture Only:
YES
(for Debug)
Add the React Native Reanimated plugin to your babel.config.js
. This must be the last plugin in the plugins array:
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
// ... other plugins
'react-native-reanimated/plugin', // ← This MUST be the last plugin
],
};
❌ Common Mistake:
// DON'T DO THIS - other plugins after reanimated plugin will cause issues
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
'react-native-reanimated/plugin',
'some-other-plugin', // ← This will break reanimated
],
};
✅ Correct Configuration:
// DO THIS - reanimated plugin as the last plugin
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
'some-other-plugin',
'another-plugin',
'react-native-reanimated/plugin', // ← Last plugin
],
};
After updating babel.config.js
, clean your project:
# For React Native
npx react-native start --reset-cache
# For Expo (if applicable)
expo start --clear
Initialize the SDK at the root level of your application using the useInitialize
hook:
import { useInitialize } from '@revrag-ai/embed-react-native';
function App() {
const { isInitialized, error } = useInitialize({
apiKey: 'YOUR_API_KEY',
embedUrl: 'YOUR_ONWID_SERVER_URL',
});
if (error) {
console.error('SDK initialization failed:', error);
}
if (!isInitialized) {
// Show loading screen while initializing
return <LoadingScreen />;
}
// Your app components
return <YourApp />;
}
Property | Type | Required | Description |
---|---|---|---|
apiKey |
string | ✅ | Your Embed API key |
embedUrl |
string | ✅ | Your Embed server URL |
GestureHandlerRootView
for the SDK to work properly:
import React from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { useInitialize } from '@revrag-ai/embed-react-native';
export default function App() {
const { isInitialized, error } = useInitialize({
apiKey: 'your_api_key_here',
embedUrl: '<https://your-embed-server.com>',
});
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/* Your app components */}
</GestureHandlerRootView>
);
}
Add the floating voice agent button to your screen:
import { EmbedButton } from '@revrag-ai/embed-react-native';
function MyScreen() {
return (
<View style={{ flex: 1 }}>
{/* Your screen content */}
<EmbedButton />
</View>
);
}
The SDK provides a powerful event system for sending user context and application state to the AI agent.
The SDK exports the following event types:
import { EventKeys } from '@revrag-ai/embed-react-native';
// Available event keys:
EventKeys.USER_DATA // 'user_data' - User identity and profile
EventKeys.SCREEN_STATE // 'state_data' - Application state and context
🚨 CRITICAL REQUIREMENT:
- USER_DATA event MUST be sent first before any other events
-
USER_DATA must include
app_user_id
for user identification - EmbedButton should only be rendered AFTER USER_DATA event is sent
- SCREEN_STATE events can only be sent after USER_DATA is established
import { embed, EventKeys } from '@revrag-ai/embed-react-native';
// Send events
await embed.Event(eventKey, data);
// Listen to events (optional)
embed.on(eventKey, callback);
Events are triggered using the embed.Event()
method and automatically:
- Validate the event key against allowed EventKeys
- Store user identity from USER_DATA events
- Auto-append app_user_id to subsequent events
- Send data to your server via the configured API
- Trigger local event listeners
// Example event flow
try {
// Step 1: Send user data first (required)
await embed.Event(EventKeys.USER_DATA, {
app_user_id: 'user123',
name: 'John Doe',
email: 'john@example.com',
});
// Step 2: Send context data (app_user_id auto-added)
await embed.Event(EventKeys.SCREEN_STATE, {
screen: 'profile',
data: { plan: 'premium' },
});
} catch (error) {
console.error('Event error:', error);
}
import React, { useEffect, useState } from 'react';
import { View, StyleSheet, Alert } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import {
useInitialize,
EmbedButton,
embed,
EventKeys
} from '@revrag-ai/embed-react-native';
export default function App() {
const [userDataSent, setUserDataSent] = useState(false);
const { isInitialized, error } = useInitialize({
apiKey: 'your_api_key_here',
embedUrl: '<https://your-embed-server.com>',
});
// Initialize user data when SDK is ready
useEffect(() => {
if (isInitialized && !userDataSent) {
initializeUserData();
}
}, [isInitialized, userDataSent]);
const initializeUserData = async () => {
try {
// STEP 1: Send user data first (REQUIRED)
await embed.Event(EventKeys.USER_DATA, {
app_user_id: 'user123', // Required field
name: 'John Doe',
email: 'john@example.com',
subscription: 'premium',
joinedDate: '2024-01-15',
});
setUserDataSent(true);
// STEP 2: Send initial screen state
await embed.Event(EventKeys.SCREEN_STATE, {
screen: 'home',
timestamp: new Date().toISOString(),
userActions: [],
});
console.log('User data and initial state sent successfully');
} catch (error) {
console.error('Failed to initialize user data:', error);
Alert.alert('Error', 'Failed to initialize voice agent');
}
};
// Send screen state updates
const updateScreenState = async (screenName, data = {}) => {
if (!userDataSent) {
console.warn('Cannot send screen state before user data');
return;
}
try {
await embed.Event(EventKeys.SCREEN_STATE, {
screen: screenName,
timestamp: new Date().toISOString(),
...data,
});
} catch (error) {
console.error('Failed to update screen state:', error);
}
};
// Handle initialization errors
if (error) {
console.error('SDK initialization failed:', error);
return (
<View style={styles.errorContainer}>
<Text>Failed to initialize voice agent</Text>
</View>
);
}
// Show loading while initializing
if (!isInitialized) {
return (
<View style={styles.loadingContainer}>
<Text>Initializing voice agent...</Text>
</View>
);
}
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.content}>
{/* Your app content */}
<YourAppComponents onScreenChange={updateScreenState} />
</View>
{/* Only render EmbedButton after user data is sent */}
{userDataSent && <EmbedButton />}
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
import { embed, EventKeys } from '@revrag-ai/embed-react-native';
// Listen for events (optional)
useEffect(() => {
// Listen for user data events
const unsubscribeUserData = embed.on(EventKeys.USER_DATA, (data) => {
console.log('User data updated:', data);
});
// Listen for screen state events
const unsubscribeScreenState = embed.on(EventKeys.SCREEN_STATE, (data) => {
console.log('Screen state changed:', data);
});
// Cleanup listeners
return () => {
// Note: Current version doesn't provide unsubscribe method
// This is for illustration of the API design
};
}, []);
import { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import { embed, EventKeys } from '@revrag-ai/embed-react-native';
function NavigationListener() {
const navigation = useNavigation();
useEffect(() => {
const unsubscribe = navigation.addListener('state', (e) => {
const routeName = e.data.state.routes[e.data.state.index].name;
// Send screen state when navigation changes
embed.Event(EventKeys.SCREEN_STATE, {
screen: routeName,
timestamp: new Date().toISOString(),
navigationStack: e.data.state.routes.map(route => route.name),
}).catch(console.error);
});
return unsubscribe;
}, [navigation]);
return null;
}
A: This is the most common issue. Ensure:
- ✅ React Native Reanimated plugin is the last plugin in
babel.config.js
- ✅ Clear cache after babel config changes:
npx react-native start --reset-cache
- ✅ Restart Metro bundler completely
- ✅ For iOS:
cd ios && pod install
// ✅ Correct babel.config.js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
'react-native-reanimated/plugin', // ← MUST be last
],
};
A: This error occurs when you try to send events before USER_DATA:
- ✅ Send
USER_DATA
event first withapp_user_id
- ✅ Wait for the event to complete before sending other events
- ✅ Only render
EmbedButton
after USER_DATA is sent
// ❌ Wrong order
await embed.Event(EventKeys.SCREEN_STATE, { screen: 'home' }); // Error!
await embed.Event(EventKeys.USER_DATA, { app_user_id: 'user123' });
// ✅ Correct order
await embed.Event(EventKeys.USER_DATA, { app_user_id: 'user123' });
await embed.Event(EventKeys.SCREEN_STATE, { screen: 'home' }); // Works!
A: Ensure permissions are configured:
Android:
- ✅
RECORD_AUDIO
andMICROPHONE
permissions inAndroidManifest.xml
- ✅ Request permissions at runtime for Android 6+
iOS:
- ✅
NSMicrophoneUsageDescription
inInfo.plist
- ✅ Provide user-friendly description
A: This crash occurs when the app tries to access the microphone without proper permission description:
Quick Fix:
- ✅ Open
ios/YourAppName/Info.plist
- ✅ Add the microphone usage description:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for voice communication with AI agent</string>
- ✅ Clean and rebuild:
cd ios && pod install && cd .. && npx react-native run-ios
Why this happens: iOS requires apps to declare why they need access to privacy-sensitive data like microphone, camera, location, etc.
A: Check these requirements:
- ✅ App wrapped with
GestureHandlerRootView
- ✅ SDK initialized successfully (
isInitialized
is true) - ✅ USER_DATA event sent first
- ✅ EmbedButton rendered after USER_DATA
A: Verify configuration:
- ✅ Valid
apiKey
andembedUrl
- ✅ Network connectivity
- ✅ Server is accessible from the device
- ✅
usesCleartextTraffic="true"
for HTTP endpoints (Android)
A: This is caused by iOS App Transport Security (ATS). Solutions:
For HTTP APIs (Development/Testing):
Add domain exceptions to ios/YourApp/Info.plist
:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<!-- Replace with your API domain -->
<key>your-api-domain.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
<!-- For localhost development -->
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
For Production (Recommended):
- ✅ Use HTTPS endpoints instead of HTTP
- ✅ Get proper SSL certificates
- ✅ Update
embedUrl
to usehttps://
NSAllowsArbitraryLoads: true
in production apps
A: Enable network debugging:
- Add logging to API calls:
// Add this to your API initialization
console.log('API URL:', embedUrl);
console.log('Making request to:', `${embedUrl}/embedded-agent/initialize`);
-
Check iOS Console logs:
- Open Xcode → Window → Devices and Simulators
- Select your device → Open Console
- Look for network-related errors
- Test network connectivity:
# Test if your API is reachable
curl -I <http://your-api-domain.com/embedded-agent/initialize>
A: Best practices for initialization:
const [initState, setInitState] = useState('loading');
const { isInitialized, error } = useInitialize({
apiKey: process.env.ONWID_API_KEY,
embedUrl: process.env.ONWID_URL,
});
useEffect(() => {
if (error) {
setInitState('error');
} else if (isInitialized) {
setInitState('ready');
}
}, [isInitialized, error]);
// Render based on state
switch (initState) {
case 'loading': return <LoadingScreen />;
case 'error': return <ErrorScreen error={error} />;
case 'ready': return <AppWithOnwid />;
}
A: Event optimization strategies:
// ✅ Debounce frequent events
const debouncedStateUpdate = useCallback(
debounce((data) => {
embed.Event(EventKeys.SCREEN_STATE, data);
}, 500),
[]
);
// ✅ Batch related data
const sendUserProfile = async (userData) => {
await embed.Event(EventKeys.USER_DATA, {
app_user_id: userData.id,
...userData.profile,
...userData.preferences,
lastLogin: new Date().toISOString(),
});
};
A: Offline handling approach:
import NetInfo from '@react-native-community/netinfo';
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsOnline(state.isConnected);
});
return () => unsubscribe();
}, []);
// Queue events when offline
const sendEventWithRetry = async (eventKey, data) => {
if (!isOnline) {
// Store in AsyncStorage for later retry
await storeEventForRetry(eventKey, data);
return;
}
try {
await embed.Event(eventKey, data);
} catch (error) {
// Retry logic or store for later
await storeEventForRetry(eventKey, data);
}
};
For additional help:
- Documentation: https://docs.revrag.ai
- Email Support: contact@revrag.ai
- GitHub Issues: https://github.com/RevRag-ai/embed-react-native/issues
If upgrading from a previous version, check the CHANGELOG.md for breaking changes and migration steps.
Last Updated: January 2024 SDK Version: Latest React Native Compatibility: 0.70+