# React Native

> 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
Markdown: /embed/integration/react-native.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 &#x2A;*`@revrag-ai/embed-react-native`** SDK adds a **voice AI agent** to your app: a &#x2A;*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*&#x2A; for the FAB via &#x2A;*`widget_config`** from device registration
* **Advanced FAB behavior** (route **groups**, show **delays**, **insets*&#x2A;, per-group rules): covered in &#x2A;*[EmbedProvider advanced](/embed/integration/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&#x29;); use &#x2A;*`@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#appendix-native-platform-setup))
* **`GestureHandlerRootView`** at the app root (`react-native-gesture-handler`)
* Peer libraries listed in [Installation](/embed/integration/react-native#installation) (LiveKit, Reanimated, Gesture Handler, Async Storage, Lottie, Safe Area, Linear Gradient)

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

***

## Installation [#installation]

### Install the package [#install-the-package]

<CodeGroup>
  ```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
  ```
</CodeGroup>

### Install peer dependencies [#install-peer-dependencies]

The SDK expects these packages in your app (versions should match what the SDK release notes recommend):

<CodeGroup>
  ```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
  ```
</CodeGroup>

### 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#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 &#x2A;*`useInitialize`** near the root (for example in `App.tsx&#x60;). It registers the device, prepares LiveKit on the JS side, and returns &#x2A;*`{ 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 <YourApp />;
}
```

### Step 3 - Wrap the app [#step-3---wrap-the-app]

1. Wrap the whole app in &#x2A;*`GestureHandlerRootView`** (required for gesture handler).
2. Wrap &#x2A;*`NavigationContainer`*&#x2A; with &#x2A;*`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 (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <EmbedProvider
        navigationRef={navigationRef}
        appVersion={packageJson.version}
        includeScreens={['Home', 'Settings']} // omit or [] = all routes (subject to server config)
      >
        <NavigationContainer ref={navigationRef}>{/* navigators */}</NavigationContainer>
      </EmbedProvider>
    </GestureHandlerRootView>
  );
}
```

### 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 &#x2A;*`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 &#x2A;*`USER_DATA`*&#x2A; succeeds with a valid &#x2A;*`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#appendix-native-platform-setup) (especially **LiveKit** `setup()` on Android and iOS).
2. Use **Step 2–3*&#x2A; above with a real &#x2A;*`apiKey`**.
3. Open a screen that is allowed by &#x2A;*`includeScreens`*&#x2A; (or omit &#x2A;*`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 &#x2A;*`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 (
      <View style={styles.centered}>
        <Text>SDK error</Text>
      </View>
    );
  }

  if (!isInitialized || !userRegistered) {
    return (
      <View style={styles.centered}>
        <Text>Preparing embed...</Text>
      </View>
    );
  }

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <EmbedProvider
        navigationRef={navigationRef}
        appVersion={packageJson.version}
        includeScreens={['Home', 'Settings']}
      >
        <NavigationContainer ref={navigationRef}>{/* Place RootNavigator here */}</NavigationContainer>
      </EmbedProvider>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  centered: { flex: 1, alignItems: 'center', justifyContent: 'center' },
});
```

### Without React Navigation [#without-react-navigation]

Mount &#x2A;*`EmbedButton`*&#x2A; yourself on screens where you want the FAB, and send &#x2A;*`SCREEN_STATE`*&#x2A; manually when the step changes. You still need &#x2A;*`useInitialize`*&#x2A;, &#x2A;*`GestureHandlerRootView`*&#x2A;, and &#x2A;*`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 &#x2A;*`{ 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/embed-provider-advanced). |

### Advanced FAB visibility and `EmbedProvider` [#advanced-fab-visibility-and-embedprovider]

Basic integration uses &#x2A;*`includeScreens`*&#x2A; (and optionally &#x2A;*`embedButtonDelayMs`**). For **route groups**, **per-group delays**, **insets**, and **continuity*&#x2A; rules, you need the expanded API. Full prop tables, JSON examples, and behavior notes are in &#x2A;*[EmbedProvider advanced](/embed/integration/embed-provider-advanced)** - treat it as the companion doc whenever the FAB must behave differently by flow or screen.

<CardGroup cols="1">
  <Card title="Recommended: full EmbedProvider advanced guide" href="/embed/integration/embed-provider-advanced">
    **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*&#x2A;, not only &#x2A;*`includeScreens`**.
  </Card>
</CardGroup>

### Server `widget_config` (FAB look and behavior) [#server-widget_config-fab-look-and-behavior]

After device registration, the SDK reads &#x2A;*`widget_config`*&#x2A; to style the FAB (avatar Lottie/image, colors, copy, corner position, paddings, nudge / inactivity behavior). &#x2A;*`EmbedProvider`** props control **when** the FAB is shown and delays/insets in your app; they **do not*&#x2A; replace &#x2A;*`widget_config`**.

Typical top-level JSON sections map to parsed types such as &#x2A;*`agentAvatar`*&#x2A;, &#x2A;*`agentTextContent`*&#x2A;, &#x2A;*`colorPalette`*&#x2A;, &#x2A;*`collapsedView`*&#x2A; (nudge / popup), and &#x2A;*`position`*&#x2A; (corner and edge padding). Exact aliases and parsing live in the package under &#x2A;*`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 &#x2A;*`SCREEN_STATE`** for custom steps (e.g. webviews)                                                           |
| **User context API** | **`Embed.Event`*&#x2A; with &#x2A;*`USER_DATA`*&#x2A;, &#x2A;*`SCREEN_STATE`*&#x2A;, &#x2A;*`CUSTOM_EVENT`*&#x2A;, &#x2A;*`ANALYTICS_DATA`**                        |
| **Agent lifecycle**  | Local &#x2A;*`AgentEvent`** listeners (`embedOnAgent` / `Embed.event.on&#x60;); mirrored to backend as &#x2A;*`analytics_data`*&#x2A; with &#x2A;*`event_name`**    |
| **FAB visibility**   | `includeScreens&#x60;, delays, or grouped config - see &#x2A;*[EmbedProvider advanced](/embed/integration/embed-provider-advanced)** for groups, insets, and delays |
| **Click tracking**   | Automatic on touchables when visibility rules allow                                                                                                                 |
| **Mic permission**   | Optional &#x2A;*`checkPermissions`*&#x2A;; listen for &#x2A;*`MICROPHONE_PERMISSION_ALLOWED`*&#x2A; / &#x2A;*`DENIED`**                                             |

***

## Events and callbacks [#events-and-callbacks]

There are **two separate systems**:

1. **`EmbedEventKeys` (data events)*&#x2A; - You call &#x2A;*`Embed.Event(key, data, onResult?)`**. On success, the SDK \*\*`PUT&#x60;\*\*s user-context updates with &#x2A;*`type`** matching the key (`user_data`, `screen_state`, `custom_event`, `analytics_data`).
2. **`AgentEvent` (agent events)*&#x2A; - The SDK emits these on &#x2A;*`Embed.event`*&#x2A; for FAB / voice / mic / popup moments. Subscribe with &#x2A;*`embedOnAgent`*&#x2A; or &#x2A;*`Embed.event.on`*&#x2A;. On the wire they appear as &#x2A;*`analytics_data`*&#x2A; with &#x2A;*`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: &#x2A;*`app_user_id`*&#x2A; plus optional &#x2A;*`data`** object       |
| **`SCREEN_STATE`**   | `{ screen, data? }` when you need explicit context                                  |
| **`CUSTOM_EVENT`**   | Arbitrary JSON-friendly &#x2A;*`data`** for product events                          |
| **`ANALYTICS_DATA`** | **`event_name` required*&#x2A;; optional &#x2A;*`data`*&#x2A; / &#x2A;*`metadata`** |

**`Embed.Event` behavior (important for first-time integrators)**

* The returned **promise does not reject*&#x2A; on HTTP failure. Always use the optional third argument &#x2A;*`onResult(success, error?)`** when you care about failure.
* On success: &#x2A;*`Embed.on(key)`*&#x2A; handlers run, then &#x2A;*`onResult(true)`**.
* On failure: &#x2A;*`Embed.on`** handlers **do not** run for that attempt.

**`AgentEvent` behavior**

* **`embedOnAgent`*&#x2A; / &#x2A;*`Embed.event.on`** run **before*&#x2A; the SDK attempts the analytics &#x2A;*`PUT`**.
* If &#x2A;*`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 &#x2A;*`AGENT_CONNECTED`*&#x2A; / &#x2A;*`AGENT_DISCONNECTED`*&#x2A; may still appear; prefer &#x2A;*`AGENT_CONVERSATION_STARTED`*&#x2A; / &#x2A;*`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 &#x2A;*`PUT`*&#x2A; payloads use a &#x2A;*`type`*&#x2A; aligned with &#x2A;*`EmbedEventKeys`*&#x2A;. Agent lifecycle is mirrored as &#x2A;*`analytics_data`*&#x2A; with &#x2A;*`event_name`*&#x2A;. For field-level contracts, open &#x2A;*`BACKEND_EVENTS.md`*&#x2A; in &#x2A;*`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 &#x2A;*`LiveKitReactNative.setup(this)`*&#x2A; in Android &#x2A;*`MainApplication.onCreate`** before React Native starts.
2. Add &#x2A;*`LiveKitReactNative.setup()`*&#x2A; in iOS &#x2A;*`AppDelegate`*&#x2A; inside &#x2A;*`didFinishLaunchingWithOptions`**.
3. Clean rebuild Android / run &#x2A;*`pod install`** on iOS, then reinstall the app.

See code snippets in [Appendix: Native platform setup](/embed/integration/react-native#appendix-native-platform-setup).

### Reanimated animations broken [#reanimated-animations-broken]

**Cause:** Babel plugin order wrong.

**Fix:*&#x2A; Put **`react-native-reanimated/plugin` last** in `babel.config.js&#x60;, then &#x2A;*`npx react-native start --reset-cache`**.

### “User identity not found” or empty backend context [#user-identity-not-found-or-empty-backend-context]

**Cause:*&#x2A; &#x2A;*`USER_DATA`*&#x2A; not sent or failed; other events need a stored &#x2A;*`app_user_id`**.

**Fix:*&#x2A; Send &#x2A;*`USER_DATA`*&#x2A; right after login with &#x2A;*`onResult`*&#x2A;. Send other &#x2A;*`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:*&#x2A; Android manifest needs &#x2A;*`RECORD_AUDIO`*&#x2A; (and related). iOS &#x2A;*`Info.plist`*&#x2A; must include &#x2A;*`NSMicrophoneUsageDescription`**. See [Appendix: Native platform setup](/embed/integration/react-native#appendix-native-platform-setup).

### FAB never appears [#fab-never-appears]

**Checklist:**

* **`GestureHandlerRootView`** wraps the tree
* **`useInitialize`*&#x2A; completed without &#x2A;*`error`**
* **`EmbedProvider`*&#x2A; wraps &#x2A;*`NavigationContainer`*&#x2A; and shares &#x2A;*`navigationRef`**
* Current route name is listed in &#x2A;*`includeScreens`*&#x2A; if you set it (omit &#x2A;*`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 &#x2A;*`NSAppTransportSecurity`*&#x2A; exceptions (never ship &#x2A;*`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#support).

<Accordion title="Network debugging (iOS)">
  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).
</Accordion>

***

## Best practices [#best-practices]

* **Initialize once*&#x2A; at the app root with &#x2A;*`useInitialize`**; avoid calling it from every screen.
* **Send `USER_DATA` as soon as you have a stable `app_user_id`*&#x2A; (typically immediately after login). Use &#x2A;*`onResult`** to surface failures.
* **Debounce*&#x2A; high-frequency &#x2A;*`SCREEN_STATE`** or analytics calls if your navigation updates rapidly.
* **Subscribe*&#x2A; to &#x2A;*`embedOnAgent`*&#x2A; in &#x2A;*`useEffect`** and **always*&#x2A; call &#x2A;*`embedOffAgent(handle)`** on cleanup (Strict Mode safe).
* **Use HTTPS** and valid TLS in production; keep cleartext exceptions dev-only.
* **Hide the FAB*&#x2A; on sensitive flows (auth, payments) with &#x2A;*`includeScreens`** or grouped visibility config.
* **Plan FAB visibility early:** If product needs **groups**, **delays**, or **insets*&#x2A; beyond a flat screen list, read &#x2A;*[EmbedProvider advanced](/embed/integration/embed-provider-advanced)** before locking UI - retrofitting rules is harder than wiring them during integration.
* **Log `event.type`*&#x2A; in development when integrating &#x2A;*`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 · &#x2A;*React Native:*&#x2A; 0.70+ · &#x2A;*`@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#installation).

### LiveKit native setup (required) [#livekit-native-setup-required]

<Warning>
  Without native &#x2A;*`LiveKitReactNative.setup`*&#x2A;, voice will fail with errors such as &#x2A;*`audioRecordSamplesDispatcher is not initialized!`**.
</Warning>

#### 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 &#x2A;*`android/app/src/main/AndroidManifest.xml`** as children of the root **manifest** element:

```xml
<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" />
```

Your **application*&#x2A; element may include &#x2A;*`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
<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>
```

For **dev-only HTTP*&#x2A; to specific hosts, add &#x2A;*`NSExceptionDomains`*&#x2A; entries. Avoid &#x2A;*`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]

<Warning>
  **`react-native-reanimated/plugin` must be the last plugin** in `babel.config.js`.
</Warning>

```javascript
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    // ...other plugins
    'react-native-reanimated/plugin',
  ],
};
```

Then:

<CodeGroup>
  ```bash React Native CLI
  npx react-native start --reset-cache
  ```

  ```bash Expo
  expo start --clear
  ```
</CodeGroup>

### Fonts and assets [#fonts-and-assets]

After native and JS setup:

```bash
npx react-native-asset
```
