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. 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_AUDIO declared in the Android app's manifest, otherwise the microphone button does not show. The camera needs no permission.
  2. 2

    Install

    npm install @clomni/react-native
    cd ios && pod install

    Autolinking does the rest. On Android, the module fetches ai.clomni:messenger from Maven Central. On iOS, pod install adds the SDK as a Swift package (React Native's spm_dependency), and Xcode downloads it on the first build.

    On React Native older than 0.75, pod install stops 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 on ClomniMessenger symbols, 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. 3

    Expo

    Expo Go cannot load native modules. Use a development build: npx expo prebuild, npx expo run:ios or run: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: true adds the aps-environment entitlement and the remote-notification background mode on iOS, and the POST_NOTIFICATIONS permission on Android. The icon becomes a drawable named clomni_notification_icon. In a plain app without Expo, write the two photo permission texts into Info.plist yourself: NSPhotoLibraryUsageDescription and NSCameraUsageDescription.

  4. 4

    Start the SDK

    Once, outside any component. index.js is 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 initialize is region, '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. 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 through addEventListener: messengerOpened, messengerClosed, conversationStarted, flowCompleted. The module itself takes the native SDK's callbacks, so do not set them separately in native code.

    Call loginUser on 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 until logout; when loginUser is called, their conversations move to the user.

  6. 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 fontFamily styles. On iOS the font must be in the app and listed under UIAppFonts; on Android React Native finds it in android/app/src/main/assets/fonts or res/font. For the font alone: Clomni.setTypeface('Montserrat'). The dashboard's value applies only where the app sets none.

  7. 7

    Push: Android

    Upload the keys to the dashboard first (“Mobile SDK push keys”). Set up Firebase with google-services.json and 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" /> in AndroidManifest.xml, put the white icon in android/app/src/main/res/drawable/ and pass its name to setNotificationIcon.

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

    1. Add the Push Notifications capability in Xcode.
    2. Add the pod 'ClomniMessenger' line above to ios/Podfile and run pod install.
    3. 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()
        }
    }
    1. 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.

    1. 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) in didRegisterForRemoteNotificationsWithDeviceToken. In Expo, prebuild creates the ios folder: keep steps 3 and 4 in a small config plugin of your own (withAppDelegate, withXcodeProject), or keep the ios folder in the repository.

  9. 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 ClomniMessenger symbols on iOS: the pod 'ClomniMessenger' line in the Podfile and pod install.
    • No notifications on Android while the app is closed: setBackgroundMessageHandler must be in index.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: MainActivity is singleTask. Fixed in SDK 1.0.2.
  10. 10

    Full guide

Document