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
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_AUDIOdeclared in the Android app's manifest, otherwise the microphone button does not show. The camera needs no permission.
-
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/Podfileinsidetarget 'Runner'and runpod install. Without the line,pod installstops 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.2NSMicrophoneUsageDescription) toios/Runner/Info.plist.On Android,
MainActivitymust extendFlutterFragmentActivity, notFlutterActivity. 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()
- 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
-
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
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.yamldo not reach native screens. PasssetTypefacethe family name of a font listed underUIAppFontson iOS, and the name of ares/fontresource on Android:await Clomni.setTypeface(Platform.isIOS ? 'Montserrat' : 'montserrat');.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. -
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);setLanguagechanges 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
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" />inandroid/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
Push: iOS
Clomni sends iOS pushes to APNs, not through Firebase.
firebase_messagingdoes not see a tap on a Clomni notification, so catch the tap in Runner's native code:- In Xcode (
ios/Runner.xcworkspace): Runner › Signing & Capabilities › + Capability › Push Notifications. - 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, productClomniMessenger, target Runner. - 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() } }- 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) } }- 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
didRegisterForRemoteNotificationsWithDeviceTokeninAppDelegate:Clomni.setDeviceToken(deviceToken), thensuper. - In Xcode (
-
8
Problems
pod installstops and prints thepod 'ClomniMessenger'line: write the line insidetarget 'Runner', or turn on Swift Package Manager.- Android: the app crashes when the Messenger opens, or the floating button does not show:
MainActivitymust extendFlutterFragmentActivity. - No notifications on Android while the app is closed: register
onBackgroundMessagebeforerunApp, 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
Full guide
- PDF: Clomni Mobile SDK guide, in English, Azerbaijani and Russian.
- Online: Flutter.
- In the dashboard: “Guide” in the channel's settings column.