# React Native (1.0.35) > Step-by-step guide to integrate the RevRag React Native embed SDK (voice agent, provider, events, configuration, and native platform setup). URL: /embed/integration/react-native/v1.0.35 Markdown: /embed/integration/react-native/v1.0.35.md # React Native embed SDK [#react-native-embed-sdk] Follow this guide in order the first time you integrate. **Native LiveKit setup is required** - skipping it is the most common source of runtime errors. *** ## Introduction [#introduction] The **`@revrag-ai/embed-react-native`** SDK adds a **voice AI agent** to your app: a **floating action button (FAB)** backed by **LiveKit**, optional **navigation-aware** visibility, and a **user-context channel** to your embed backend (`PUT .../user-context/update`). **Latest published package version:** **1.0.35** (install with `npm install @revrag-ai/embed-react-native@latest` or your package manager’s equivalent). Confirm the current version on [npm](https://www.npmjs.com/package/@revrag-ai/embed-react-native) before you pin a release in production. **What you get out of the box** * **Realtime voice** with the agent through the FAB * **Screen and app context** for richer conversations (route tracking via `EmbedProvider`, optional explicit `SCREEN_STATE`) * **Event tracking**: host-driven analytics and custom payloads via `Embed.Event`, plus **agent lifecycle** signals (`AgentEvent`) * **Best-effort click tracking** on touchables when the widget visibility rules allow it (importing the package wires this safely; failures should not crash your app) * **Server-driven UI** for the FAB via **`widget_config`** from device registration * **Advanced FAB behavior** (route **groups**, show **delays**, **insets**, per-group rules): covered in **[EmbedProvider advanced](/embed/integration/react-native/v1.0.35/embed-provider-advanced)** - read it once you move past a simple `includeScreens` list Import only from the **package entry** (`@revrag-ai/embed-react-native`). Do not rely on deep imports from `src/` unless your team explicitly supports them. *** ## Prerequisites [#prerequisites] Before you install, confirm your environment: | Requirement | Notes | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Node.js** | 18+ recommended | | **React Native** | 0.70 or higher | | **iOS** | iOS 13+ | | **Android** | API 21+ | | **Navigation** | `@react-navigation/native` is the typical setup for `EmbedProvider` (optional if you mount `EmbedButton` manually) | | **Embed package** | **`@revrag-ai/embed-react-native`** — latest **1.0.35** on npm ([package page](https://www.npmjs.com/package/@revrag-ai/embed-react-native)); use **`@latest`** or pin a version in CI | **You will also need:** * Microphone permission (declared on both platforms; see [Appendix: Native platform setup](/embed/integration/react-native/v1.0.35#appendix-native-platform-setup)) * **`GestureHandlerRootView`** at the app root (`react-native-gesture-handler`) * Peer libraries listed in [Installation](/embed/integration/react-native/v1.0.35#installation) (LiveKit, Reanimated, Gesture Handler, Async Storage, Lottie, Safe Area, Linear Gradient) The SDK runs **polyfills** on import for Hermes / LiveKit safety. Audio and networking must be correctly configured or voice will fail silently or with native errors. *** ## Installation [#installation] ### Install the package [#install-the-package] ```bash npm npm install @revrag-ai/embed-react-native ``` ```bash yarn yarn add @revrag-ai/embed-react-native ``` ```bash pnpm pnpm add @revrag-ai/embed-react-native ``` ### Install peer dependencies [#install-peer-dependencies] The SDK expects these packages in your app (versions should match what the SDK release notes recommend): ```bash npm 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 cd ios && pod install && cd .. ``` ```bash 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 ``` ```bash 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 ``` ### Complete native setup (required) [#complete-native-setup-required] Android and iOS need **LiveKit native initialization**, permissions, Lottie on Android, Reanimated Babel config, and related steps. Those are **easy to miss** - work through [Appendix: Native platform setup](/embed/integration/react-native/v1.0.35#appendix-native-platform-setup) once, then rebuild the app. After native steps, run: ```bash npx react-native-asset ``` *** ## Basic setup (step-by-step) [#basic-setup-step-by-step] Do these steps **in order** for a standard React Navigation app. ### Step 1 - Import the SDK [#step-1---import-the-sdk] ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useInitialize, EmbedProvider, Embed, EmbedEventKeys } from '@revrag-ai/embed-react-native'; ``` You will also use your navigation library and (recommended) `package.json` for `appVersion`. ### Step 2 - Initialize the SDK once [#step-2---initialize-the-sdk-once] Call **`useInitialize`** near the root (for example in `App.tsx`). It registers the device, prepares LiveKit on the JS side, and returns **`{ isInitialized, error }`**. ```tsx import { useInitialize } from '@revrag-ai/embed-react-native'; export default function App() { const { isInitialized, error } = useInitialize({ apiKey: 'YOUR_EMBED_API_KEY', // embedUrl: 'https://your-embed-host', // optional; omit to use SDK default host }); if (error) { // Show an error UI or retry } if (!isInitialized) { // Optional: splash / loading until the SDK is ready } return ; } ``` ### Step 3 - Wrap the app [#step-3---wrap-the-app] 1. Wrap the whole app in **`GestureHandlerRootView`** (required for gesture handler). 2. Wrap **`NavigationContainer`** with **`EmbedProvider`**, passing the **same ref** you attach to `NavigationContainer`. The provider **mounts the FAB** for you. You usually do **not** import `EmbedButton` separately. ```tsx import { useRef } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useInitialize, EmbedProvider } from '@revrag-ai/embed-react-native'; import packageJson from './package.json'; export default function App() { const navigationRef = useRef(null); const { isInitialized, error } = useInitialize({ apiKey: 'YOUR_EMBED_API_KEY' }); if (error || !isInitialized) { return null; // replace with loading / error UI } return ( {/* navigators */} ); } ``` ### Step 4 - Configure keys and register the user [#step-4---configure-keys-and-register-the-user] | Item | Where | Purpose | | ----------------- | ----------------------------- | ---------------------------------------------------------------- | | **`apiKey`** | `useInitialize` | Authenticates your app with the embed backend | | **`embedUrl`** | `useInitialize` (optional) | Overrides the default embed API host | | **`appVersion`** | `EmbedProvider` | Sent with analytics / context (use your real app version) | | **`app_user_id`** | `Embed.Event(USER_DATA, ...)` | Stable user id after login; **required** for most backend writes | After you know the signed-in user, send **`USER_DATA`** once (or again after account switch): ```tsx await Embed.Event( EmbedEventKeys.USER_DATA, { app_user_id: user.id, data: { name: user.name, email: user.email }, }, (success, err) => { if (!success) console.warn('USER_DATA failed:', err); } ); ``` Until **`USER_DATA`** succeeds with a valid **`app_user_id`**, many **backend** updates for other event types may be skipped or cannot be built. The FAB can still render; fix registration if analytics or context look empty. *** ## Usage example [#usage-example] ### Minimal flow to verify the integration [#minimal-flow-to-verify-the-integration] 1. Finish [Appendix: Native platform setup](/embed/integration/react-native/v1.0.35#appendix-native-platform-setup) (especially **LiveKit** `setup()` on Android and iOS). 2. Use **Step 2–3** above with a real **`apiKey`**. 3. Open a screen that is allowed by **`includeScreens`** (or omit **`includeScreens`** to allow all routes). 4. You should see the **FAB**; start a call to confirm **microphone** permission and audio. ### Larger example (navigation + `USER_DATA`) [#larger-example-navigation--user_data] This pattern waits for SDK init, registers the user with **`onResult`**, then renders navigation inside the provider. ```tsx import React, { useEffect, useRef, useState } from 'react'; import { View, StyleSheet, Text, Alert } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { NavigationContainer } from '@react-navigation/native'; import { useInitialize, EmbedProvider, Embed, EmbedEventKeys, } from '@revrag-ai/embed-react-native'; import packageJson from './package.json'; export default function App() { const navigationRef = useRef(null); const [userRegistered, setUserRegistered] = useState(false); const { isInitialized, error } = useInitialize({ apiKey: 'your_api_key_here', }); useEffect(() => { if (!isInitialized || userRegistered) return; void Embed.Event( EmbedEventKeys.USER_DATA, { app_user_id: 'user_123', data: { name: 'Test User' }, }, (success, err) => { if (success) setUserRegistered(true); else Alert.alert('Embed', 'USER_DATA failed: ' + (err ?? 'unknown')); } ); }, [isInitialized, userRegistered]); if (error) { return ( SDK error ); } if (!isInitialized || !userRegistered) { return ( Preparing embed... ); } return ( {/* Place RootNavigator here */} ); } const styles = StyleSheet.create({ centered: { flex: 1, alignItems: 'center', justifyContent: 'center' }, }); ``` ### Without React Navigation [#without-react-navigation] Mount **`EmbedButton`** yourself on screens where you want the FAB, and send **`SCREEN_STATE`** manually when the step changes. You still need **`useInitialize`**, **`GestureHandlerRootView`**, and **`USER_DATA`** for full backend behavior. *** ## Configuration options [#configuration-options] ### `useInitialize(options)` [#useinitializeoptions] | Option | Type | Required | Description | | -------------- | -------- | -------- | ----------------------------------------------- | | **`apiKey`** | `string` | Yes | Embed API key for your app | | **`embedUrl`** | `string` | No | Override embed host; default comes from the SDK | Returns **`{ isInitialized, error }`**. Call **once** near the root. ### `EmbedProvider` props [#embedprovider-props] | Prop | Type | Required | Description | | --------------------------------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | | **`children`** | `ReactNode` | Yes | Usually your `NavigationContainer` and trees below it | | **`navigationRef`** | ref | Recommended | Ref passed to `NavigationContainer` for route tracking | | **`appVersion`** | `string` | Yes | Semantic app version for analytics / context | | **`includeScreens`** | `string[]` | No | Route **names** where the FAB may show; omit or `[]` for all (subject to server config) | | **`embedButtonDelayMs`** | `number` | No | Delay before showing the FAB after a screen becomes eligible | | **`embedButtonVisibilityConfig`** | object | No | Grouped visibility, per-group delays, insets. See [advanced guide](/embed/integration/react-native/v1.0.35/embed-provider-advanced). | ### Advanced FAB visibility and `EmbedProvider` [#advanced-fab-visibility-and-embedprovider] Basic integration uses **`includeScreens`** (and optionally **`embedButtonDelayMs`**). For **route groups**, **per-group delays**, **insets**, and **continuity** rules, you need the expanded API. Full prop tables, JSON examples, and behavior notes are in **[EmbedProvider advanced](/embed/integration/react-native/v1.0.35/embed-provider-advanced)** - treat it as the companion doc whenever the FAB must behave differently by flow or screen. **Important for production UX.** Same guide as the **Recommended** card in **Step 3 - Wrap the app** above. Use when you configure **groups**, **delays**, and **insets**, not only **`includeScreens`**. ### Server `widget_config` (FAB look and behavior) [#server-widget_config-fab-look-and-behavior] After device registration, the SDK reads **`widget_config`** to style the FAB (avatar Lottie/image, colors, copy, corner position, paddings, nudge / inactivity behavior). **`EmbedProvider`** props control **when** the FAB is shown and delays/insets in your app; they **do not** replace **`widget_config`**. Typical top-level JSON sections map to parsed types such as **`agentAvatar`**, **`agentTextContent`**, **`colorPalette`**, **`collapsedView`** (nudge / popup), and **`position`** (corner and edge padding). Exact aliases and parsing live in the package under **`src/api/types/widget.config.types.ts`** (use that file as the backend contract). *** ## Features overview [#features-overview] | Area | What it does | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Voice session** | LiveKit realtime audio from the FAB | | **Screen context** | Provider + ref track the current route; optional **`SCREEN_STATE`** for custom steps (e.g. webviews) | | **User context API** | **`Embed.Event`** with **`USER_DATA`**, **`SCREEN_STATE`**, **`CUSTOM_EVENT`**, **`ANALYTICS_DATA`** | | **Agent lifecycle** | Local **`AgentEvent`** listeners (`embedOnAgent` / `Embed.event.on`); mirrored to backend as **`analytics_data`** with **`event_name`** | | **FAB visibility** | `includeScreens`, delays, or grouped config - see **[EmbedProvider advanced](/embed/integration/react-native/v1.0.35/embed-provider-advanced)** for groups, insets, and delays | | **Click tracking** | Automatic on touchables when visibility rules allow | | **Mic permission** | Optional **`checkPermissions`**; listen for **`MICROPHONE_PERMISSION_ALLOWED`** / **`DENIED`** | *** ## Events and callbacks [#events-and-callbacks] There are **two separate systems**: 1. **`EmbedEventKeys` (data events)** - You call **`Embed.Event(key, data, onResult?)`**. On success, the SDK \*\*`PUT`\*\*s user-context updates with **`type`** matching the key (`user_data`, `screen_state`, `custom_event`, `analytics_data`). 2. **`AgentEvent` (agent events)** - The SDK emits these on **`Embed.event`** for FAB / voice / mic / popup moments. Subscribe with **`embedOnAgent`** or **`Embed.event.on`**. On the wire they appear as **`analytics_data`** with **`event_name`** set to the agent string. The SDK **does not** push HTTP acknowledgements back into JS. For “same moment as the write”, use **local** callbacks below. ### `EmbedEventKeys` (only these four) [#embedeventkeys-only-these-four] | Key | Typical use | | -------------------- | ----------------------------------------------------------------------------------- | | **`USER_DATA`** | After login: **`app_user_id`** plus optional **`data`** object | | **`SCREEN_STATE`** | `{ screen, data? }` when you need explicit context | | **`CUSTOM_EVENT`** | Arbitrary JSON-friendly **`data`** for product events | | **`ANALYTICS_DATA`** | **`event_name` required**; optional **`data`** / **`metadata`** | **`Embed.Event` behavior (important for first-time integrators)** * The returned **promise does not reject** on HTTP failure. Always use the optional third argument **`onResult(success, error?)`** when you care about failure. * On success: **`Embed.on(key)`** handlers run, then **`onResult(true)`**. * On failure: **`Embed.on`** handlers **do not** run for that attempt. **`AgentEvent` behavior** * **`embedOnAgent`** / **`Embed.event.on`** run **before** the SDK attempts the analytics **`PUT`**. * If **`app_user_id`** is not in storage, the **HTTP mirror may be skipped**, but **listeners still ran**. ### Quick reference [#quick-reference] | Mechanism | When it runs | Notes | | ------------------------------------ | -------------------------------------- | -------------------------------------- | | **`Embed.Event(..., onResult)`** | Every data event call | Use for per-call success/failure | | **`Embed.on` / `Embed.off`** | After **successful** send for that key | Cross-cutting reactions | | **`embedOnAgent` / `embedOffAgent`** | On each **`AgentEvent` emit** | Runs even if backend mirror is skipped | | **`Embed.event.on` / `off`** | Single agent event | Remember cleanup in `useEffect` | ### Example: subscribe to all agent events [#example-subscribe-to-all-agent-events] ```tsx import { useEffect } from 'react'; import { embedOnAgent, embedOffAgent, AgentEvent } from '@revrag-ai/embed-react-native'; useEffect(() => { const handle = embedOnAgent((event) => { switch (event.type) { case AgentEvent.AGENT_CONVERSATION_STARTED: break; case AgentEvent.AGENT_CONVERSATION_ENDED: break; case AgentEvent.POPUP_MESSAGE_VISIBLE: break; case AgentEvent.MICROPHONE_PERMISSION_DENIED: break; default: break; } }); return () => embedOffAgent(handle); }, []); ``` Deprecated aliases **`AGENT_CONNECTED`** / **`AGENT_DISCONNECTED`** may still appear; prefer **`AGENT_CONVERSATION_STARTED`** / **`AGENT_CONVERSATION_ENDED`**. ### Example: after successful `SCREEN_STATE` sends [#example-after-successful-screen_state-sends] ```tsx import Embed, { EmbedEventKeys } from '@revrag-ai/embed-react-native'; const onScreen = (data: unknown) => { /* runs only after a successful API send */ }; Embed.on(EmbedEventKeys.SCREEN_STATE, onScreen); // Embed.off(EmbedEventKeys.SCREEN_STATE, onScreen); ``` ### Backend payload types (short) [#backend-payload-types-short] User-context **`PUT`** payloads use a **`type`** aligned with **`EmbedEventKeys`**. Agent lifecycle is mirrored as **`analytics_data`** with **`event_name`**. For field-level contracts, open **`BACKEND_EVENTS.md`** in **`node_modules/@revrag-ai/embed-react-native`** after install. *** ## Troubleshooting [#troubleshooting] ### `audioRecordSamplesDispatcher is not initialized!` [#audiorecordsamplesdispatcher-is-not-initialized] **Cause:** LiveKit native setup is missing. **Fix:** 1. Add **`LiveKitReactNative.setup(this)`** in Android **`MainApplication.onCreate`** before React Native starts. 2. Add **`LiveKitReactNative.setup()`** in iOS **`AppDelegate`** inside **`didFinishLaunchingWithOptions`**. 3. Clean rebuild Android / run **`pod install`** on iOS, then reinstall the app. See code snippets in [Appendix: Native platform setup](/embed/integration/react-native/v1.0.35#appendix-native-platform-setup). ### Reanimated animations broken [#reanimated-animations-broken] **Cause:** Babel plugin order wrong. **Fix:** Put **`react-native-reanimated/plugin` last** in `babel.config.js`, then **`npx react-native start --reset-cache`**. ### “User identity not found” or empty backend context [#user-identity-not-found-or-empty-backend-context] **Cause:** **`USER_DATA`** not sent or failed; other events need a stored **`app_user_id`**. **Fix:** Send **`USER_DATA`** right after login with **`onResult`**. Send other **`Embed.Event`** calls after you know registration succeeded (or handle failures explicitly). ### Microphone does not work or iOS crashes on mic access [#microphone-does-not-work-or-ios-crashes-on-mic-access] **Fix:** Android manifest needs **`RECORD_AUDIO`** (and related). iOS **`Info.plist`** must include **`NSMicrophoneUsageDescription`**. See [Appendix: Native platform setup](/embed/integration/react-native/v1.0.35#appendix-native-platform-setup). ### FAB never appears [#fab-never-appears] **Checklist:** * **`GestureHandlerRootView`** wraps the tree * **`useInitialize`** completed without **`error`** * **`EmbedProvider`** wraps **`NavigationContainer`** and shares **`navigationRef`** * Current route name is listed in **`includeScreens`** if you set it (omit **`includeScreens`** to test “all routes”) ### Network / ATS errors on iOS [#network--ats-errors-on-ios] Use HTTPS in production. For development-only HTTP, add careful **`NSAppTransportSecurity`** exceptions (never ship **`NSAllowsArbitraryLoads: true`** for production). See plist examples in the appendix. ### Still stuck? [#still-stuck] Use the expandable section below for network debugging tips, or see [Support](/embed/integration/react-native/v1.0.35#support). 1. Xcode → Window → Devices and Simulators → open Console for the device. 2. From a machine: `curl -I https://your-api-domain.com/embedded-agent/initialize` (replace with your host). *** ## Best practices [#best-practices] * **Initialize once** at the app root with **`useInitialize`**; avoid calling it from every screen. * **Send `USER_DATA` as soon as you have a stable `app_user_id`** (typically immediately after login). Use **`onResult`** to surface failures. * **Debounce** high-frequency **`SCREEN_STATE`** or analytics calls if your navigation updates rapidly. * **Subscribe** to **`embedOnAgent`** in **`useEffect`** and **always** call **`embedOffAgent(handle)`** on cleanup (Strict Mode safe). * **Use HTTPS** and valid TLS in production; keep cleartext exceptions dev-only. * **Hide the FAB** on sensitive flows (auth, payments) with **`includeScreens`** or grouped visibility config. * **Plan FAB visibility early:** If product needs **groups**, **delays**, or **insets** beyond a flat screen list, read **[EmbedProvider advanced](/embed/integration/react-native/v1.0.35/embed-provider-advanced)** before locking UI - retrofitting rules is harder than wiring them during integration. * **Log `event.type`** in development when integrating **`AgentEvent`**; payloads can vary by call site. *** ## Support [#support] * **Docs:** [https://docs.revrag.ai](https://docs.revrag.ai/) * **Email:** [contact@revrag.ai](mailto:contact@revrag.ai) * **Package README:** [npm](https://www.npmjs.com/package/@revrag-ai/embed-react-native) **Last updated:** April 2026 · **React Native:** 0.70+ · **`@revrag-ai/embed-react-native`:** 1.0.35 (see [npm](https://www.npmjs.com/package/@revrag-ai/embed-react-native) for newer releases) *** ## Appendix: Native platform setup [#appendix-native-platform-setup] Complete these steps on a fresh integration. They complement [Installation](/embed/integration/react-native/v1.0.35#installation). ### LiveKit native setup (required) [#livekit-native-setup-required] Without native **`LiveKitReactNative.setup`**, voice will fail with errors such as **`audioRecordSamplesDispatcher is not initialized!`**. #### Android (`MainApplication.kt`) [#android-mainapplicationkt] ```kotlin import com.livekit.reactnative.LiveKitReactNative class MainApplication : Application(), ReactApplication { override fun onCreate() { super.onCreate() LiveKitReactNative.setup(this) // ... } } ``` #### iOS (`AppDelegate.swift`) [#ios-appdelegateswift] ```swift import LiveKitReactNative func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { LiveKitReactNative.setup() // ... return true } ``` Then clean rebuild: ```bash cd android && ./gradlew clean && cd .. && npx react-native run-android cd ios && pod install && cd .. && npx react-native run-ios ``` ### Android manifest permissions [#android-manifest-permissions] Add to **`android/app/src/main/AndroidManifest.xml`** as children of the root **manifest** element: ```xml ``` Your **application** element may include **`android:usesCleartextTraffic="true"`** only if you truly need HTTP in dev. ### Android Lottie (`build.gradle`) [#android-lottie-buildgradle] ```groovy dependencies { implementation 'com.airbnb.android:lottie:6.0.1' } ``` #### App size (ABI splits) [#app-size-abi-splits] LiveKit increases native binary size. For production APK size, see [Android app size optimization](/embed/integration/android-app-size-optimization). ### ProGuard (Android release) [#proguard-android-release] ```text # Embed SDK -keep class com.revrag.embed.** { *; } -keep class org.webrtc.** { *; } -dontwarn org.webrtc.** # Lottie -keep class com.airbnb.lottie.** { *; } ``` ### iOS permissions (`Info.plist`) [#ios-permissions-infoplist] ```xml NSMicrophoneUsageDescription This app needs access to microphone for voice communication with AI agent NSAppTransportSecurity NSAllowsArbitraryLoads NSAllowsLocalNetworking ``` For **dev-only HTTP** to specific hosts, add **`NSExceptionDomains`** entries. Avoid **`NSAllowsArbitraryLoads: true`** in production. ### iOS pods and build settings [#ios-pods-and-build-settings] ```bash cd ios && pod install && cd .. ``` If builds fail: set **Bitcode** to **NO**, **Build Active Architecture Only** to **YES** (Debug). ### Babel (Reanimated) [#babel-reanimated] **`react-native-reanimated/plugin` must be the last plugin** in `babel.config.js`. ```javascript module.exports = { presets: ['module:@react-native/babel-preset'], plugins: [ // ...other plugins 'react-native-reanimated/plugin', ], }; ``` Then: ```bash React Native CLI npx react-native start --reset-cache ``` ```bash Expo expo start --clear ``` ### Fonts and assets [#fonts-and-assets] After native and JS setup: ```bash npx react-native-asset ```