Adding the Mobile SDK to a Flutter app

Last updated: 9 October 2026 · 9 min read

The clomni_flutter plugin sits on top of the native Android and iOS SDKs, and the screens are native screens.

Steps

  1. 1

    Requirements

    • Flutter 3.16 or newer.
    • iOS 15, Android API 24.
    • 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

    flutter pub add clomni_flutter

    The iOS SDK is a Swift package:

    • With Swift Package Manager, the plugin fetches the SDK itself. It is on by default in current Flutter. On Flutter 3.24 and later, if it is off, turn it on with flutter config --enable-swift-package-manager.
    • With CocoaPods, add this line to ios/Podfile inside target 'Runner' and run pod install. Without the line, pod install stops with “clomni_flutter: the iOS SDK is not on CocoaPods trunk”.
    pod 'ClomniMessenger', :git => 'https://github.com/clomni/clomni-mobile-sdk.git', :tag => '1.0.4'

    Add the photo permission texts (NSPhotoLibraryUsageDescription, NSCameraUsageDescription, and from SDK 1.0.2 NSMicrophoneUsageDescription) to ios/Runner/Info.plist.

    On Android, MainActivity must extend FlutterFragmentActivity, not FlutterActivity. Otherwise the app crashes when the Messenger opens, and the floating button does not show:

    // android/app/src/main/kotlin/com/example/app/MainActivity.kt
    package com.example.app
    
    import io.flutter.embedding.android.FlutterFragmentActivity
    
    class MainActivity : FlutterFragmentActivity()
  3. 3

    Start the SDK

    import 'dart:io' show Platform;
    
    import 'package:clomni_flutter/clomni_flutter.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Clomni.initialize('app_…', Platform.isIOS ? 'ios_…' : 'android_…');
      if (kDebugMode) await Clomni.setLogLevel(ClomniLogLevel.debug);
      runApp(const App());
    }
  4. 4

    User and Messenger

    await Clomni.loginUser(
      const ClomniUser(userId: '12345', email: '[email protected]', name: 'Aysel'),
      userHash: hashFromYourServer,
    );
    await Clomni.updateUser(language: 'az', customAttributes: {'plan': 'premium'});
    
    // Together with your app's own logout:
    await Clomni.logout();
    class SupportTile extends StatelessWidget {
      const SupportTile({super.key});
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<int>(
          stream: Clomni.unreadCountStream, // the current count at once, then every change
          builder: (context, snapshot) {
            final unread = snapshot.data ?? 0;
            return ListTile(
              title: const Text('Support'),
              trailing: unread > 0 ? Badge(label: Text('$unread')) : null,
              onTap: () => Clomni.present(source: 'profile_support'),
            );
          },
        );
      }
    }

    Other calls: presentNewConversation(source: …), presentConversation(id), dismiss(), setLauncherVisible(true), setBottomPadding(72). Events are streams: onMessengerOpened, onMessengerClosed, onConversationStarted, onFlowCompleted.

    Fonts in pubspec.yaml do not reach native screens. Pass setTypeface the family name of a font listed under UIAppFonts on iOS, and the name of a res/font resource on Android: await Clomni.setTypeface(Platform.isIOS ? 'Montserrat' : 'montserrat');.

    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.

  5. 5

    Flows, events, links and look

    Starting a flow from the app and listening to the messenger's events:

    await Clomni.startFlow('ride_problem', data: {'ride_id': 'R-1923'}, openMessenger: true, source: 'ride_screen');
    
    final subscriptions = [
      Clomni.onMessengerOpened.listen((source) => debugPrint('opened from $source')),
      Clomni.onMessengerClosed.listen((_) => debugPrint('closed')),
      Clomni.onConversationStarted.listen((id) => debugPrint('conversation $id')),
      Clomni.onFlowCompleted.listen((flowId) => debugPrint('flow $flowId')),
    ];
    // later: for (final s in subscriptions) { s.cancel(); }

    The flow must be built in the dashboard with an "App event" trigger of the same name and published. The data must be JSON.

    Links, the messenger's language, sounds and the app's own colour:

    import 'package:url_launcher/url_launcher.dart';
    
    await Clomni.onLink((url) => launchUrl(Uri.parse(url)));   // or your own router; null: the system opens links
    await Clomni.setLanguage('en');                             // 'az', 'en', 'ru'; null follows the phone
    await Clomni.setSoundsEnabled(false);
    await Clomni.setTheme(primaryColor: '#0A66C2', typeface: 'Montserrat', mode: ClomniThemeMode.dark);

    setLanguage changes the language the messenger speaks; updateUser(language: …) stores the language on the user's profile. The chosen language must be on in the dashboard, otherwise the phone's language is used, and if that is not on either, the dashboard's main language. The colour must be #RRGGBB. The dashboard's values apply only where the app gives none of its own.

  6. 6

    Push: Android

    Upload the keys to the dashboard first (“Mobile SDK push keys”). Set up Firebase with FlutterFire (flutterfire configure) and declare <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> in android/app/src/main/AndroidManifest.xml.

    import 'dart:io' show Platform;
    
    import 'package:clomni_flutter/clomni_flutter.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter/material.dart';
    
    // Clomni data messages while the app is in the background or closed: the SDK shows the notification.
    @pragma('vm:entry-point')
    Future<void> onBackgroundMessage(RemoteMessage message) async {
      await Clomni.handlePush(message.data);
    }
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
      FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
      await Clomni.initialize('app_…', Platform.isIOS ? 'ios_…' : 'android_…');
      runApp(const App());
    }
    
    Future<void> setUpAndroidPush() async {
      final messaging = FirebaseMessaging.instance;
      await messaging.requestPermission(); // Android 13+: the system permission dialog
      final token = await messaging.getToken();
      if (token != null) await Clomni.setDeviceToken(token);
      messaging.onTokenRefresh.listen(Clomni.setDeviceToken);
      // Messages while the app is open.
      FirebaseMessaging.onMessage.listen((message) async {
        if (!await Clomni.handlePush(message.data)) {
          // your app's own push
        }
      });
      await Clomni.setNotificationIcon('ic_notification'); // android/app/src/main/res/drawable/ic_notification.png
    }

    Call setUpAndroidPush() on Android at a suitable moment, for example after login.

  7. 7

    Push: iOS

    Clomni sends iOS pushes to APNs, not through Firebase. firebase_messaging does not see a tap on a Clomni notification, so catch the tap in Runner's native code:

    1. In Xcode (ios/Runner.xcworkspace): Runner › Signing & Capabilities › + Capability › Push Notifications.
    2. Runner's own code imports ClomniMessenger. With CocoaPods, the Podfile line above is enough. With Swift Package Manager, add the package to the Runner target too: File › Add Package Dependencies, https://github.com/clomni/clomni-mobile-sdk.git, product ClomniMessenger, target Runner.
    3. Add this file to Runner:
    // ios/Runner/ClomniNotifications.swift
    import ClomniMessenger
    import UserNotifications
    
    /// Clomni pushes: hidden while the Messenger is open, and a tap opens the conversation.
    final class ClomniNotifications: NSObject, UNUserNotificationCenterDelegate {
        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 AppDelegate.swift, before `super.application(...)`:
    // ios/Runner/AppDelegate.swift
    import Flutter
    import UIKit
    import UserNotifications
    
    @main
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        UNUserNotificationCenter.current().delegate = ClomniNotifications.shared
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
    1. Pass the APNs token from Dart:
    if (Platform.isIOS) {
      await FirebaseMessaging.instance.requestPermission();
      final apnsToken = await FirebaseMessaging.instance.getAPNSToken(); // hex
      if (apnsToken != null) await Clomni.setDeviceToken(apnsToken);
    }

    Without Firebase on iOS, override didRegisterForRemoteNotificationsWithDeviceToken in AppDelegate: Clomni.setDeviceToken(deviceToken), then super.

  8. 8

    Problems

    • pod install stops and prints the pod 'ClomniMessenger' line: write the line inside target 'Runner', or turn on Swift Package Manager.
    • Android: the app crashes when the Messenger opens, or the floating button does not show: MainActivity must extend FlutterFragmentActivity.
    • No notifications on Android while the app is closed: register onBackgroundMessage before runApp, with a top-level function.
    • iOS: No such module 'ClomniMessenger': add the package or the pod to the Runner target (step 2).
    • On iOS a tap only opens the app: the delegate is missing or set after super.application(...).

    Dart errors start with [Clomni] in the Flutter log; native logs are in logcat (Clomni) and the Xcode console.

  9. 9

    Full guide

Document