Adding the Mobile SDK to an Android app
Last updated: 9 October 2026 · 11 min read
The Android SDK is written in Kotlin and works from Java too. It asks for no permission beyond network access and adds nothing to your screens. From 1.0.2 a conversation can take photos and videos with the camera and send voice messages. The camera needs no permission: the SDK opens the phone's own camera app, and the FileProvider is inside the SDK; if the app's manifest declares CAMERA, the SDK asks for it first. Voice messages need RECORD_AUDIO, declared by the app in its own AndroidManifest.xml; without it the microphone button does not show, and the SDK says so in the log.
Steps
-
1
Requirements
- Android 6.0 (API 23) or newer,
compileSdk35 or newer, Kotlin 1.8 or newer. - The App ID and the Android API key (
android_…) from the dashboard's Installation section.
- Android 6.0 (API 23) or newer,
-
2
Add the package
The package is on Maven Central. Most projects already have
mavenCentral()insettings.gradle.kts.// app/build.gradle.kts dependencies { implementation("ai.clomni:messenger:1.0.5") } -
3
Start the SDK
Call
initializeonce, in yourApplicationclass. A notification can start the app's process, and the Messenger it opens needs a ready SDK, so anActivityis too late.// App.kt import android.app.Application import ai.clomni.messenger.Clomni import ai.clomni.messenger.ClomniLogLevel class App : Application() { override fun onCreate() { super.onCreate() Clomni.initialize(this, appId = "app_…", apiKey = "android_…") if (BuildConfig.DEBUG) Clomni.setLogLevel(ClomniLogLevel.DEBUG) } }<!-- AndroidManifest.xml --> <application android:name=".App" ...>With AGP 8 or newer,
BuildConfig.DEBUGneedsbuildFeatures { buildConfig = true }inapp/build.gradle.kts. You can callClomnifrom any thread, listeners are called on the main thread. A secondinitializeis ignored. -
4
Sign the user in
After your own login, with the
user_hashfrom your server:val user = ClomniUser(userId = "12345", email = "[email protected]", name = "Aysel") Clomni.loginUser(user, userHash = hashFromYourServer) Clomni.updateUser(language = "az", customAttributes = mapOf("plan" to "premium")) // Together with your app's own logout: Clomni.logout()
The server code for the hash is in “Mobile SDK keys and identifying users”.
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
Open the Messenger
findViewById<View>(R.id.support).setOnClickListener { Clomni.present(source = "profile_support") } // Jetpack Compose Button(onClick = { Clomni.present(source = "profile_support") }) { Text("Support") }Clomni.presentNewConversation(source): straight to a new conversation.Clomni.presentConversation(id): a known conversation, the ID comes fromonConversationStarted.Clomni.dismiss(): closes the Messenger from code.
The unread count, to show next to your button:
class ProfileActivity : AppCompatActivity() { private val unread = UnreadCountListener { count -> supportBadge.isVisible = count > 0 supportBadge.text = count.toString() } override fun onStart() { super.onStart() Clomni.addUnreadCountListener(unread) } override fun onStop() { Clomni.removeUnreadCountListener(unread) super.onStop() } }The floating button is off by default:
Clomni.setLauncherVisible(true), andClomni.setBottomPadding(72)(dp) to lift it above a bottom navigation bar. -
6
Events, links, language and look
To listen to the Messenger's events:
Clomni.onMessengerOpened { source -> Log.d("App", "opened from $source") } Clomni.onMessengerClosed { Log.d("App", "closed") } Clomni.onConversationStarted { id -> Log.d("App", "conversation $id") } Clomni.onFlowCompleted { flowId -> Log.d("App", "flow $flowId completed") } Clomni.onUnreadCountChanged { count -> Log.d("App", "unread $count") }Each event has one listener: a new one replaces the old one,
nullremoves it.A news item's button can carry a web address or your app's deep link. With
onLinkset, the link comes to your app first. Returntruewhen the app opened it;false, or no listener, lets the system open it.Clomni.onLink { url -> if (url.startsWith("example://")) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).setPackage(packageName)) true } else { false } }Language, sounds and your app's own colour:
Clomni.setLanguage("en") // az, en or ru; null follows the phone Clomni.setSoundsEnabled(false) // off whatever the panel says Clomni.setTheme( primaryColor = "#0A66C2", typeface = ResourcesCompat.getFont(context, R.font.montserrat), mode = ClomniThemeMode.DARK, // LIGHT, DARK or SYSTEM )setLanguagepicks one of the languages turned on in the dashboard (Appearance › “Languages”). An empty value, or a language that is off, follows the phone's language, then the dashboard's main language.- Sounds: a short sound for a message sent or received while a conversation is open. It follows the phone's silent mode.
setThemeputs your app's own colour, font and mode over the dashboard's. The colour is#RRGGBB; the other brand colours are derived from it. Each call replaces the previous one, and a value left out stays the dashboard's. With a wrong format, the log sayssetTheme: primaryColor "…" is not #RRGGBB.- For the font alone:
Clomni.setTypeface(typeface). The font still follows the user's text size setting.
-
7
Push: add Firebase to the app
Agents' replies reach a closed app as FCM data messages. The SDK has no Firebase dependency: your app keeps its own
firebase-messaginglibrary, and your own pushes stay yours.Firebase console › your project › Project Overview › + Add app › Android: enter the package name, “Register app”, download
google-services.jsonand put it in theapp/folder.// settings.gradle.kts (or the root build.gradle.kts) plugins { id("com.google.gms.google-services") version "4.4.2" apply false }// app/build.gradle.kts plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.gms.google-services") } dependencies { implementation("ai.clomni:messenger:1.0.5") implementation(platform("com.google.firebase:firebase-bom:33.4.0")) implementation("com.google.firebase:firebase-messaging") }The dashboard does not take
google-services.json; it takes the project's service account JSON file: see “Mobile SDK push keys”. -
8
Push: pass the token and the messages
// AppMessagingService.kt import ai.clomni.messenger.Clomni import ai.clomni.messenger.ClomniPush import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class AppMessagingService : FirebaseMessagingService() { override fun onNewToken(token: String) { Clomni.setDeviceToken(token) // and to your own server, if the app has its own pushes } override fun onMessageReceived(message: RemoteMessage) { if (ClomniPush.handle(this, message.data)) return // your app's own push } }<!-- AndroidManifest.xml, inside <application> --> <service android:name=".AppMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>FCM calls
onNewTokenonly when the token changes. Pass the current token at every launch too, inApplication.onCreateafterinitialize:FirebaseMessaging.getInstance().token.addOnSuccessListener(Clomni::setDeviceToken)
An app has only one
FirebaseMessagingService. If you already have one, pass the Clomni messages in that service.ClomniPush.isClomniPush(data)tells whether a push is Clomni's without handling it. -
9
Push: Android 13 permission and the icon
On Android 13 and newer, a notification needs the
POST_NOTIFICATIONSpermission. The SDK never asks for it: declare it in the manifest and ask at a sensible moment, for example after the first message.<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
class MainActivity : AppCompatActivity() { private val askNotifications = registerForActivityResult(ActivityResultContracts.RequestPermission()) { } private fun askForNotifications() { if (Build.VERSION.SDK_INT >= 33 && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED ) { askNotifications.launch(Manifest.permission.POST_NOTIFICATIONS) } } }Without the permission no notification is shown, but the unread count still updates. The small icon is your app's icon. If it is not a silhouette, Android draws a grey square: pass a white silhouette icon with
Clomni.setNotificationIcon(R.drawable.ic_notification).The user sees one notification per conversation: the title is the agent and the brand, the text is the message itself (up to 180 characters). The notification channel is
clomni_messages. A tap opens the app with that conversation on top. While the Messenger is open, Clomni notifications are not shown. -
10
Check it
- Run the app on a phone, allow notifications and open the Messenger once.
- Dashboard › your channel › settings column › Push › “Test push” › choose the device › “Send”.
Logs are in logcat with the tag
Clomni. Common errors:call Clomni.initialize first: callinitializeinApplication.onCreateand name the class in the manifest withandroid:name.- A log line starting with
api_keythat says the key is wrong or not for this platform (server errorinvalid_api_key; the SDK writes this line in Azerbaijani): use theandroid_…key and check it was not revoked. notification not shown (POST_NOTIFICATIONS?): ask for the Android 13+ permission.- Your service's
onMessageReceivedis never called: the service is missing from the manifest, or anotherFirebaseMessagingServicetakes the messages.
From Java:
Clomni.initialize(context, "app_…", "android_…");,Clomni.loginUser(new ClomniUser("12345", "[email protected]"), hashFromYourServer);,Clomni.present("profile_support");. -
11
Full guide
- PDF: Clomni Mobile SDK guide, in English, Azerbaijani and Russian.
- Online: Android.
- In the dashboard: “Guide” in the channel's settings column.