@revrag-ai/embed-react-native
TypeScript icon, indicating that this package has built-in type declarations

1.0.13 • Public • Published

Embed React Native SDK Integration Guide

Overview

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.

Table of Contents

  1. Installation
  2. Peer Dependencies
  3. Android Configuration
  4. iOS Configuration
  5. Babel Configuration
  6. SDK Initialization
  7. App Setup
  8. Event System
  9. Usage Examples
  10. FAQ & Troubleshooting

Installation

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

Peer Dependencies

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 ..

Alternative installation commands:

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

Android Configuration

1. Android Manifest Permissions

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>

2. Build.gradle Configuration

Add Lottie dependency to your android/app/build.gradle:

dependencies {
    implementation 'com.airbnb.android:lottie:6.0.1'
    // ... other dependencies
}

3. ProGuard Configuration (if using ProGuard)

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.** { *; }

iOS Configuration

1. iOS Permissions

🚨 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>

⚠️ App Crash Fix: If your app crashes with "attempted to access privacy-sensitive data without a usage description", ensure the NSMicrophoneUsageDescription key is present in your Info.plist.

2. Pod Installation

After installing peer dependencies, run:

cd ios
pod install
cd ..

3. iOS Build Settings (if needed)

If you encounter build issues, add these to your iOS project settings:

  • Enable Bitcode: NO
  • Build Active Architecture Only: YES (for Debug)

Babel Configuration

⚠️ CRITICAL: React Native Reanimated Configuration

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

SDK Initialization

1. useInitialize Hook

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 />;
}

2. Configuration Options

Property Type Required Description
apiKey string Your Embed API key
embedUrl string Your Embed server URL

App Setup

1. Wrap App with GestureHandlerRootView

⚠️ IMPORTANT: You must wrap your entire app with 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>
  );
}

2. Add EmbedButton Component

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>
  );
}

Event System

The SDK provides a powerful event system for sending user context and application state to the AI agent.

Available Events

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

Event Usage Rules

🚨 CRITICAL REQUIREMENT:

  1. USER_DATA event MUST be sent first before any other events
  2. USER_DATA must include app_user_id for user identification
  3. EmbedButton should only be rendered AFTER USER_DATA event is sent
  4. SCREEN_STATE events can only be sent after USER_DATA is established

Event Methods

import { embed, EventKeys } from '@revrag-ai/embed-react-native';

// Send events
await embed.Event(eventKey, data);

// Listen to events (optional)
embed.on(eventKey, callback);

How Events are Triggered

Events are triggered using the embed.Event() method and automatically:

  1. Validate the event key against allowed EventKeys
  2. Store user identity from USER_DATA events
  3. Auto-append app_user_id to subsequent events
  4. Send data to your server via the configured API
  5. 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);
}

Usage Examples

Complete Integration Example

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',
  },
});

Event Listening Example

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
  };
}, []);

Advanced Usage with Navigation

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;
}

FAQ & Troubleshooting

🔴 Critical Issues

Q: "react-native-reanimated not working" or animation issues

A: This is the most common issue. Ensure:

  1. ✅ React Native Reanimated plugin is the last plugin in babel.config.js
  2. ✅ Clear cache after babel config changes: npx react-native start --reset-cache
  3. ✅ Restart Metro bundler completely
  4. ✅ 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
  ],
};

Q: "User identity not found" error

A: This error occurs when you try to send events before USER_DATA:

  1. ✅ Send USER_DATA event first with app_user_id
  2. ✅ Wait for the event to complete before sending other events
  3. ✅ 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!

Q: Microphone permission denied

A: Ensure permissions are configured:

Android:

  • RECORD_AUDIO and MICROPHONE permissions in AndroidManifest.xml
  • ✅ Request permissions at runtime for Android 6+

iOS:

  • NSMicrophoneUsageDescription in Info.plist
  • ✅ Provide user-friendly description

Q: iOS App Crashes - "attempted to access privacy-sensitive data without a usage description"

A: This crash occurs when the app tries to access the microphone without proper permission description:

Quick Fix:

  1. ✅ Open ios/YourAppName/Info.plist
  2. ✅ Add the microphone usage description:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for voice communication with AI agent</string>
  1. ✅ 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.

🟡 Common Issues

Q: EmbedButton not appearing

A: Check these requirements:

  1. ✅ App wrapped with GestureHandlerRootView
  2. ✅ SDK initialized successfully (isInitialized is true)
  3. ✅ USER_DATA event sent first
  4. ✅ EmbedButton rendered after USER_DATA

Q: Network/API connection issues

A: Verify configuration:

  1. ✅ Valid apiKey and embedUrl
  2. ✅ Network connectivity
  3. ✅ Server is accessible from the device
  4. usesCleartextTraffic="true" for HTTP endpoints (Android)

Q: iOS Network Request Failures - "The resource could not be loaded"

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):

  1. ✅ Use HTTPS endpoints instead of HTTP
  2. ✅ Get proper SSL certificates
  3. ✅ Update embedUrl to use https://

⚠️ Never use NSAllowsArbitraryLoads: true in production apps

Q: Network debugging on iOS

A: Enable network debugging:

  1. 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`);
  1. Check iOS Console logs:
    • Open Xcode → Window → Devices and Simulators
    • Select your device → Open Console
    • Look for network-related errors
  2. Test network connectivity:
# Test if your API is reachable
curl -I <http://your-api-domain.com/embedded-agent/initialize>

🟢 Best Practices

Q: How to handle SDK initialization in different app states?

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 />;
}

Q: How to optimize event sending?

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(),
  });
};

Q: How to handle offline scenarios?

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);
  }
};

📞 Support

For additional help:

🔄 Migration Guide

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+

Package Sidebar

Install

npm i @revrag-ai/embed-react-native

Weekly Downloads

26

Version

1.0.13

License

MIT

Unpacked Size

312 kB

Total Files

133

Last publish

Collaborators

  • revrag-ai