# React Native

> Add Kustomer Chat to a React Native app with @kustomer/chat-react-native. Installation for iOS and Android, configure, opening the chat UI, events and hooks, identity, push, and the platform differences to know about.

Source: https://developer.kustomer.com/docs/chat/react-native

`@kustomer/chat-react-native` wraps the native [iOS](https://developer.kustomer.com/docs/chat/ios) and [Android](https://developer.kustomer.com/docs/chat/android) SDKs, so your app gets the native chat UI on each platform from one JavaScript API.

|                       | Version                                                      |
| --------------------- | ------------------------------------------------------------ |
| Package               | `@kustomer/chat-react-native` 6.3.6                          |
| Native SDKs inside    | iOS 8.3.0, Android 4.6.2                                     |
| Built and tested with | React Native 0.77, React 18, New Architecture with Hermes    |
| iOS                   | 15.0+ (15.1+ on React Native 0.77)                           |
| Android               | minSdk 24, compileSdk 35, Kotlin 2.2+                        |
| Expo                  | Not in Expo Go. Use a development build with native changes. |

> **Use the native UI:** The React Native package is built around the prebuilt chat UI. Theming and custom headless UIs need the native SDKs: customize colors natively, as described on the [iOS](https://developer.kustomer.com/docs/chat/ios#theming) and [Android](https://developer.kustomer.com/docs/chat/android#theming) pages.

## Install

#### Add the package

```bash
npm install @kustomer/chat-react-native
```

For push notifications, also install `@react-native-firebase/app` and `@react-native-firebase/messaging`.

#### iOS

Set the platform in your `Podfile` and install pods:

```ruby title="ios/Podfile"
platform :ios, '15.1'
```

```bash
cd ios && pod install
```

Add usage descriptions for attachments to `Info.plist`: `NSCameraUsageDescription`, `NSPhotoLibraryUsageDescription`, `NSPhotoLibraryAddUsageDescription` and `NSMicrophoneUsageDescription`.

#### Android

The Android SDK needs Kotlin 2.2 or later and, on minSdk 24 and 25, core library desugaring:

```groovy title="android/build.gradle"
buildscript {
  ext {
    kotlinVersion = "2.2.21"
  }
}
```

```groovy title="android/app/build.gradle"
android {
  compileOptions {
    coreLibraryDesugaringEnabled true
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }
}
dependencies {
  coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
```

The build stops with a clear error if `kotlinVersion` is older than 2.2.0 or isn't a full `MAJOR.MINOR.PATCH` version.

#### Configure

```tsx title="App.tsx"
import { useEffect } from 'react'
import KustomerChat from '@kustomer/chat-react-native'

export default function App() {
  useEffect(() => {
    KustomerChat.configure(KUSTOMER_CHAT_KEY, { brandId: 'YOUR_BRAND_ID' }).catch((e) =>
      console.error('Kustomer configure failed', e),
    )
  }, [])

  return <Root />
}
```

Use a chat key with only the `org.tracking` role. Call `configure` once, early. `useIsKustomerConfigured()` returns `true` once it's done.

#### Open chat

```tsx
<Button title="Help" onPress={() => KustomerChat.show()} />
```

### React Native 0.77 on Android

These are needed on React Native 0.77 with AGP 8.7, as in Kustomer's example app:

- In `android/gradle.properties`, set `react.internal.disableJavaVersionAlignment=true`.
- In `android/settings.gradle`, override R8 to 8.10.21 or later.
- If the build reports a duplicate `META-INF/versions/9/OSGI-INF/MANIFEST.MF`, add it to `packaging.resources.pickFirsts`.

## Options

| Option                      | Type                          | Platforms | Description                                                      |
| --------------------------- | ----------------------------- | --------- | ---------------------------------------------------------------- |
| `brandId`                   | `string`                      | Both      | Brand whose chat settings to use                                 |
| `businessScheduleId`        | `string`                      | Both      | Business schedule for availability                               |
| `language`                  | `KustomerLanguage`            | Both      | Force the UI language, such as `en_us`                           |
| `hideNewConversationButton` | `boolean`                     | Both      | Hide **New conversation**                                        |
| `hideKbArticleShare`        | `boolean`                     | Both      | Hide sharing on knowledge base articles                          |
| `showInAppNotifications`    | `boolean`                     | Both      | Banners for new messages while the app is open. Default `false`. |
| `enableUrlUnfurling`        | `boolean`                     | Both      | Link previews. Default `true`.                                   |
| `enableHistoryStatusFilter` | `boolean`                     | Both      | Status filter on the history screen                              |
| `historyStatusFilter`       | `KustomerHistoryStatusFilter` | Both      | Initial filter: `none`, `open`, `closed` or `requiresAttention`  |
| `logLevel`                  | `KustomerLogType`             | Both      | `error`, `info`, `debug` or `all`                                |
| `disablePush`               | `boolean`                     | Both      | Turn off push                                                    |
| `skipFCMPushRegistration`   | `boolean`                     | Both      | Register push tokens yourself. See [Push](#push-notifications).  |
| `pushEnvironment`           | `PushEnvironment`             | iOS       | `PushEnvironment.development` while testing with debug builds    |
| `maxChatAttachmentTotalMB`  | `number`                      | iOS       | Total attachment size per message. Default 50.                   |

## Open the UI

```ts
import KustomerChat, { KustomerDisplayMode } from '@kustomer/chat-react-native'

KustomerChat.show()
KustomerChat.show(KustomerDisplayMode.onlyKnowledgeBase)

const conversation = await KustomerChat.startNewConversation(
  { body: 'Where is my order?', direction: 'user' },
  { orderNumberStr: '4417' },
)

await KustomerChat.openConversation('6716a3a9c4e5f60012ab34c1')
KustomerChat.showKbArticle('ARTICLE_ID')
await KustomerChat.close()
```

Don't `await` `show()` or the `showKb*` methods: their promises don't resolve consistently across platforms.

| `KustomerDisplayMode`                    | iOS | Android                 |
| ---------------------------------------- | --- | ----------------------- |
| `default`, `chatHistory`                 | Yes | Yes                     |
| `knowledgeBaseHome`                      | Yes | Yes                     |
| `onlyChat`, `onlyKnowledgeBase`          | Yes | Yes                     |
| `newChat`, `activeChat`, `knowledgeBase` | Yes | Falls back to `default` |

On Android, chat opens as a separate Activity, so your app goes to the background and JavaScript timers pause while it's open.

### Other methods on KustomerChat

| Method                                                                            | Returns                                                                                                                                          |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `getUnreadCount()`                                                                | `Promise<number>`                                                                                                                                |
| `getOpenConversationCount()`                                                      | `Promise<number>`: conversations not ended, with a message in the last 30 days                                                                   |
| `isChatAvailable()`                                                               | `Promise<boolean>`                                                                                                                               |
| `isVisible()`                                                                     | `Promise<boolean>`                                                                                                                               |
| `openChatAssistant(assistantId, startDialog?, initialMessages?, customDescribe?)` | Opens chat with an assistant. Android ignores `startDialog`.                                                                                     |
| `handoff(params)`                                                                 | Imports a conversation that started elsewhere. Same rules as [the web SDK](https://developer.kustomer.com/docs/chat/web-core-api#conversations). |

To create a conversation without opening the UI, use `KustomerChatProvider.createConversation`:

```ts
import { KustomerChatProvider } from '@kustomer/chat-react-native'

const { conversation } = await KustomerChatProvider.createConversation({
  firstMessage: 'I need help with my order',
  title: 'Order 4417',
  customDescribe: { orderNumberStr: '4417' },
})
```

> **Stick to the documented methods:** The package's TypeScript types declare more provider methods (`KustomerChatProvider`, `KustomerKbProvider`, `KustomerPushProvider`) than the native bridges implement. Several are missing on iOS or never settle on Android. Only the methods on this page work on both platforms.

## Events

```tsx
import { useCallback, useState } from 'react'
import { useKustomerEvent } from '@kustomer/chat-react-native'

function UnreadBadge() {
  const [count, setCount] = useState(0)
  const onTotal = useCallback(({ count }: { count: number }) => setCount(count), [])
  useKustomerEvent('onUnreadTotalChange', onTotal)
  return count ? <Badge value={count} /> : null
}
```

`useKustomerEvent` re-subscribes when the callback changes, so wrap it in `useCallback`. Outside components, use `KustomerEventEmitter.addListener(event, cb)`, which returns a subscription with `remove()`. Subscribe after `configure` resolves.

| Event                                                                                            | Payload                               |
| ------------------------------------------------------------------------------------------------ | ------------------------------------- |
| `onChatMessageReceived`                                                                          | The message                           |
| `onUnreadTotalChange`                                                                            | `{ count }`                           |
| `onUnreadCountChange`                                                                            | `{ conversationId, count }`           |
| `onAgentIsTyping`                                                                                | `{ conversationId, typingIndicator }` |
| `onAgentJoined`                                                                                  | The agent                             |
| `onConversationCreated`, `onConversationEnded`, `onConversationResumed`, `onConversationDeleted` | The conversation                      |
| `onConversationMerged`                                                                           | `{ source, target }`                  |
| `onSatisfactionEventReceived`                                                                    | The CSAT survey                       |
| `onOpenConversationCountChanged`                                                                 | `{ count }`                           |
| `onSessionOpened`, `onSessionCustomAttributesUpdated`                                            | The conversation                      |

Payloads differ slightly between platforms for `onAgentJoined`, `onAssistantEnded` and `onSatisfactionEventReceived`; read fields defensively.

## Identity

```ts
const signedIn = await KustomerChat.isLoggedIn('email', user.email)
if (!signedIn) await KustomerChat.logIn(jwtFromYourServer)

await KustomerChat.describeCustomer(
  { sharedPhones: ['+15555550123'] },
  { loyaltyTierStr: 'gold' },
)

// on sign-out
await KustomerChat.logOut()
```

| Method                                             | Notes                                                                     |
| -------------------------------------------------- | ------------------------------------------------------------------------- |
| `logIn(jwt)`                                       | Await it before calling `show()`                                          |
| `isLoggedIn('email' \| 'userId', id)`              | Checks against a specific user                                            |
| `logOutThenLogIn(jwt)`                             | Switch users in one call                                                  |
| `logOut()`                                         | Clears chat from the device and deregisters push                          |
| `describeCustomer(attributes, customAttributes)`   | `sharedEmails`, `sharedPhones` and social handles, plus custom attributes |
| `describeConversation(conversationId, attributes)` | Custom attributes on a conversation                                       |
| `useIsKustomerLoggedIn(idType, id)`                | Hook that polls login state                                               |

See [Customer identity](https://developer.kustomer.com/docs/chat/identity) for generating the JWT.

## Push notifications

**Android** uses Firebase Cloud Messaging and is automatic: with `@react-native-firebase/messaging` installed, `configure` registers the FCM token, re-registers on refresh, and handles foreground messages. To do it yourself, set `skipFCMPushRegistration: true`:

```ts
import messaging from '@react-native-firebase/messaging'

messaging().getToken().then(KustomerChat.setPushToken)
messaging().onTokenRefresh(KustomerChat.setPushToken)
messaging().onMessage((message) => {
  if (KustomerChat.isKustomerNotification(message)) KustomerChat.onRemoteMessage(message)
})
```

`setPushToken` and `onRemoteMessage` are Android only.

**iOS** uses APNs and needs native code in your `AppDelegate`, the same as the native SDK. The automatic FCM registration only works on Android, so set `skipFCMPushRegistration: Platform.OS === 'ios'`. See [Push notifications](https://developer.kustomer.com/docs/chat/push-notifications#react-native).

## Troubleshooting

| Symptom                                                                    | Fix                                                                                                   |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| "The package doesn't seem to be linked"                                    | Rebuild the native app after installing, and run `pod install`. Expo Go isn't supported.              |
| Android: Kotlin version error from the Kustomer module                     | Set `ext.kotlinVersion` to 2.2.0 or later, as a full version.                                         |
| Android: R8 or Java version errors on React Native 0.77                    | Apply the [0.77 changes](#react-native-077-on-android).                                               |
| `configure` rejects with "already configured with different API key" (iOS) | `configure` ran twice with different settings. Call it once.                                          |
| Install fails with an engine error                                         | The package declares Node 24+. Upgrade Node, or use a package manager that doesn't enforce `engines`. |
