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. 1

    Requirements

    • Android 6.0 (API 23) or newer, compileSdk 35 or newer, Kotlin 1.8 or newer.
    • The App ID and the Android API key (android_…) from the dashboard's Installation section.
  2. 2

    Add the package

    The package is on Maven Central. Most projects already have mavenCentral() in settings.gradle.kts.

    // app/build.gradle.kts
    dependencies {
        implementation("ai.clomni:messenger:1.0.5")
    }
  3. 3

    Start the SDK

    Call initialize once, in your Application class. A notification can start the app's process, and the Messenger it opens needs a ready SDK, so an Activity is 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.DEBUG needs buildFeatures { buildConfig = true } in app/build.gradle.kts. You can call Clomni from any thread, listeners are called on the main thread. A second initialize is ignored.

  4. 4

    Sign the user in

    After your own login, with the user_hash from 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 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

    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 from onConversationStarted.
    • 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), and Clomni.setBottomPadding(72) (dp) to lift it above a bottom navigation bar.

  6. 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, null removes it.

    A news item's button can carry a web address or your app's deep link. With onLink set, the link comes to your app first. Return true when 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
    )
    • setLanguage picks 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.
    • setTheme puts 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 says setTheme: primaryColor "…" is not #RRGGBB.
    • For the font alone: Clomni.setTypeface(typeface). The font still follows the user's text size setting.
  7. 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-messaging library, and your own pushes stay yours.

    Firebase console › your project › Project Overview › + Add app › Android: enter the package name, “Register app”, download google-services.json and put it in the app/ 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. 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 onNewToken only when the token changes. Pass the current token at every launch too, in Application.onCreate after initialize:

    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. 9

    Push: Android 13 permission and the icon

    On Android 13 and newer, a notification needs the POST_NOTIFICATIONS permission. 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. 10

    Check it

    1. Run the app on a phone, allow notifications and open the Messenger once.
    2. 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: call initialize in Application.onCreate and name the class in the manifest with android:name.
    • A log line starting with api_key that says the key is wrong or not for this platform (server error invalid_api_key; the SDK writes this line in Azerbaijani): use the android_… key and check it was not revoked.
    • notification not shown (POST_NOTIFICATIONS?): ask for the Android 13+ permission.
    • Your service's onMessageReceived is never called: the service is missing from the manifest, or another FirebaseMessagingService takes the messages.

    From Java: Clomni.initialize(context, "app_…", "android_…");, Clomni.loginUser(new ClomniUser("12345", "[email protected]"), hashFromYourServer);, Clomni.present("profile_support");.

  11. 11

    Full guide

Document