Adding the Mobile SDK to a React Native or Expo app
Last updated: 9 October 2026 · 10 min read
The @clomni/react-native package sits on top of the native Android and iOS SDKs, and the screens are native screens.
Steps
-
1
Requirements
- React Native 0.75 or newer (0.76 for the New Architecture). Expo SDK 52 or newer, with a development build.
- iOS 15, Android 6.0 (API 23).
- The App ID and both API keys (
android_…,ios_…). - Voice messages (from SDK 1.0.2) need
RECORD_AUDIOdeclared in the Android app's manifest, otherwise the microphone button does not show. The camera needs no permission.
-
2
Install
npm install @clomni/react-native cd ios && pod install
Autolinking does the rest. On Android, the module fetches
ai.clomni:messengerfrom Maven Central. On iOS,pod installadds the SDK as a Swift package (React Native'sspm_dependency), and Xcode downloads it on the first build.On React Native older than 0.75,
pod installstops with “@clomni/react-native needs React Native 0.75 or later”. Update React Native, or add the line below to the Podfile. Add the same line when linking fails onClomniMessengersymbols, or when you wire push in native code:# ios/Podfile, in your app's target pod 'ClomniMessenger', :git => 'https://github.com/clomni/clomni-mobile-sdk.git', :tag => '1.0.4'
-
3
Expo
Expo Go cannot load native modules. Use a development build:
npx expo prebuild,npx expo run:iosorrun:android, or an EAS build. The package's config plugin writes what the native projects need:// app.config.ts export default { // ... plugins: [ ['@clomni/react-native', { photoLibraryPermission: 'To send photos to support', // iOS cameraPermission: 'To take a photo and send it to support', // iOS push: true, // the default notificationIcon: './assets/notification-icon.png', // Android, white silhouette PNG }], ], };push: trueadds theaps-environmententitlement and theremote-notificationbackground mode on iOS, and thePOST_NOTIFICATIONSpermission on Android. The icon becomes a drawable namedclomni_notification_icon. In a plain app without Expo, write the two photo permission texts intoInfo.plistyourself:NSPhotoLibraryUsageDescriptionandNSCameraUsageDescription. -
4
Start the SDK
Once, outside any component.
index.jsis a good place, because a push can start the app without a screen.// index.js import { AppRegistry, Platform } from 'react-native'; import Clomni from '@clomni/react-native'; import App from './App'; import { name as appName } from './app.json'; Clomni.initialize('app_…', Platform.OS === 'ios' ? 'ios_…' : 'android_…'); if (__DEV__) Clomni.setLogLevel('debug'); AppRegistry.registerComponent(appName, () => App);The third parameter of
initializeisregion,'eu'by default.'eu'is the only region today, so there is no need to pass it: the SDK logs any other value and uses'eu'. -
5
User and Messenger
Clomni.loginUser({ userId: '12345', email: '[email protected]', name: 'Aysel' }, hashFromYourServer); Clomni.updateUser({ language: 'az', customAttributes: { plan: 'premium' } }); // Together with your app's own logout: Clomni.logout();import { useEffect, useState } from 'react'; import { Pressable, Text } from 'react-native'; import Clomni from '@clomni/react-native'; export function SupportRow() { const [unread, setUnread] = useState(0); useEffect(() => { // The current count at once, then every change. const subscription = Clomni.addEventListener('unreadCountChanged', setUnread); return () => subscription.remove(); }, []); return ( <Pressable onPress={() => Clomni.present('profile_support')}> <Text>Support{unread > 0 ? ` (${unread})` : ''}</Text> </Pressable> ); }Other calls:
presentNewConversation(source),presentConversation(id),dismiss(),setLauncherVisible(true),setBottomPadding(72). Events throughaddEventListener:messengerOpened,messengerClosed,conversationStarted,flowCompleted. The module itself takes the native SDK's callbacks, so do not set them separately in native code.Call
loginUseron every app start while the user is signed in, as if they had just signed in. For the same user the SDK reuses its stored session.A user who has not signed in needs no call: the Messenger makes an anonymous visitor itself when it opens.
Clomni.loginUnidentifiedUser()does it in advance. The visitor stays the same on this device untillogout; whenloginUseris called, their conversations move to the user. -
6
Flows, events, links and look
Starting a flow from the app and listening to the Messenger's events:
Clomni.startFlow('ride_problem', { ride_id: 'R-1923' }, { openMessenger: true, source: 'ride_screen' }); const subscriptions = [ Clomni.addEventListener('messengerOpened', (source) => console.log('opened from', source)), Clomni.addEventListener('messengerClosed', () => console.log('closed')), Clomni.addEventListener('conversationStarted', (id) => console.log('conversation', id)), Clomni.addEventListener('flowCompleted', (flowId) => console.log('flow', flowId)), ]; // later: subscriptions.forEach((s) => s.remove());The flow needs the “App event” trigger with the same name in the dashboard, and must be published. The data must be JSON. The module owns the native SDK's event callbacks: do not set them in the app's native code as well.
Links, language, sounds and your app's own colour:
import { Linking } from 'react-native'; Clomni.onLink((url) => Linking.openURL(url)); // or your own router; null: the system opens links Clomni.setLanguage('en'); // 'az', 'en', 'ru'; null follows the phone Clomni.setSoundsEnabled(false); Clomni.setTheme({ primaryColor: '#0A66C2', typeface: 'Montserrat', mode: 'dark' });The font is the family name you use in
fontFamilystyles. On iOS the font must be in the app and listed underUIAppFonts; on Android React Native finds it inandroid/app/src/main/assets/fontsorres/font. For the font alone:Clomni.setTypeface('Montserrat'). The dashboard's value applies only where the app sets none. -
7
Push: Android
Upload the keys to the dashboard first (“Mobile SDK push keys”). Set up Firebase with
google-services.jsonand the Google services Gradle plugin, as the React Native Firebase docs describe. Then:// index.js, next to initialize import messaging from '@react-native-firebase/messaging'; // Clomni data messages while the app is in the background or closed: the SDK shows the notification. messaging().setBackgroundMessageHandler(async (message) => { Clomni.handlePush(message.data ?? {}); });// App.tsx import { useEffect } from 'react'; import { PermissionsAndroid, Platform } from 'react-native'; import messaging from '@react-native-firebase/messaging'; import Clomni from '@clomni/react-native'; useEffect(() => { if (Platform.OS !== 'android') return; PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS); // Android 13+ messaging().getToken().then((token) => Clomni.setDeviceToken(token)); const unsubscribeToken = messaging().onTokenRefresh((token) => Clomni.setDeviceToken(token)); // Messages while the app is open. const unsubscribeMessage = messaging().onMessage(async (message) => { if (!Clomni.handlePush(message.data ?? {})) { // your app's own push } }); Clomni.setNotificationIcon('clomni_notification_icon'); // the config plugin's notificationIcon return () => { unsubscribeToken(); unsubscribeMessage(); }; }, []);In a plain app, declare
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />inAndroidManifest.xml, put the white icon inandroid/app/src/main/res/drawable/and pass its name tosetNotificationIcon. -
8
Push: iOS
Clomni sends iOS pushes straight to APNs, not through Firebase. The Firebase library only reports FCM messages and does not see a tap on a Clomni notification. So catch the tap in native code:
- Add the Push Notifications capability in Xcode.
- Add the
pod 'ClomniMessenger'line above toios/Podfileand runpod install. - Add this file to the app target:
// ios/<YourApp>/ClomniNotifications.swift import ClomniMessenger import UserNotifications /// Clomni pushes: hidden while the Messenger is open, and a tap opens the conversation. @objc(ClomniNotifications) final class ClomniNotifications: NSObject, UNUserNotificationCenterDelegate { @objc static let shared = ClomniNotifications() func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { let show = Clomni.shouldShowForeground(notification.request.content.userInfo) completionHandler(show ? [.banner, .list, .sound] : []) } func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { _ = Clomni.handlePush(response.notification.request.content.userInfo) completionHandler() } }- Make it the delegate in
application(_:didFinishLaunchingWithOptions:), before the method returns:
// AppDelegate.swift UNUserNotificationCenter.current().delegate = ClomniNotifications.shared
// AppDelegate.mm, when the app delegate is Objective-C #import "YourApp-Swift.h" #import <UserNotifications/UserNotifications.h> [UNUserNotificationCenter currentNotificationCenter].delegate = ClomniNotifications.shared;
React Native Firebase then takes the delegate over and forwards every push that is not its own to this delegate, so your FCM pushes keep working.
- Pass the APNs token from JavaScript:
if (Platform.OS === 'ios') { await messaging().requestPermission(); const apnsToken = await messaging().getAPNSToken(); // hex if (apnsToken) Clomni.setDeviceToken(apnsToken); }Without Firebase on iOS, pass the token in native code:
Clomni.setDeviceToken(deviceToken)indidRegisterForRemoteNotificationsWithDeviceToken. In Expo,prebuildcreates theiosfolder: keep steps 3 and 4 in a small config plugin of your own (withAppDelegate,withXcodeProject), or keep theiosfolder in the repository. -
9
Problems
- “The package doesn't seem to be linked” or the native module is not found: rebuild the app; in Expo, use a development build.
- Link errors on
ClomniMessengersymbols on iOS: thepod 'ClomniMessenger'line in the Podfile andpod install. - No notifications on Android while the app is closed:
setBackgroundMessageHandlermust be inindex.js, outside components. - On iOS, a tap on a Clomni notification only opens the app: the native delegate (steps 3 and 4) is missing or set too late.
- The floating button does not show on a cold start of the app. Cause: a bug in SDK 1.0.1, fixed in 1.0.2. Until then, open the Messenger from your own button.
- Android: the open Messenger disappears when the app is opened again from its icon. Cause:
MainActivityissingleTask. Fixed in SDK 1.0.2.
-
10
Full guide
- PDF: Clomni Mobile SDK guide, in English, Azerbaijani and Russian.
- Online: React Native.
- In the dashboard: “Guide” in the channel's settings column.