Skip to content
reception

Search documentation

Loading documentation…

    Log in
    Browse documentation

    App integration

    Android

    Connect Reception to a native Android app built with Jetpack Compose or Views. The setup prompt guides placement, user details and the design round; this page supplies its Kotlin code. For JavaScript apps, use React Native; for Dart apps, use Flutter. Both packages add Android with their next releases: reception_flutter 1.2.0 and react-native-reception 1.6.0.

    View Markdown

    Requires Android 7.0 (API 24) or later, compileSdk 35 or later, Kotlin 2.0 or later and Android Gradle Plugin 8. The SDK draws its chat with Jetpack Compose and Material 3; View-based apps use it without writing Compose code. Android SDK 1.0.0 has the features of iOS SDK 1.7.0.

    1. Install

    Add the dependency to the app module’s build.gradle.kts. The SDK is on Maven Central, which Android projects list in settings.gradle.kts by default:

    app/build.gradle.kts
    dependencies {
        implementation("sh.reception:reception:1.0.0")
    }

    With a version catalog, add reception = { module = "sh.reception:reception", version = "1.0.0" } under [libraries] and use implementation(libs.reception). In a Groovy build.gradle, write implementation 'sh.reception:reception:1.0.0'. Sync Gradle afterwards.

    The SDK needs no manifest edits, no ProGuard or R8 rules and no other dependency. It uses Firebase Cloud Messaging and Google Play Billing only when your app already includes them. Its manifest adds the INTERNET, ACCESS_NETWORK_STATE and POST_NOTIFICATIONS permissions to your app; the notification permission is requested only by the chat’s permission card, if you turn it on.

    2. Configure

    Configure once in your Application.onCreate, before any activity uses Reception. Keep your other startup work:

    MyApplication.kt
    import android.app.Application
    import sh.reception.Reception
    
    class MyApplication : Application() {
        override fun onCreate() {
            super.onCreate()
            // Reception support chat. Docs for changes: https://reception.sh/llms.txt
            Reception.configure(this, "app_YOUR_APP_ID")
        }
    }

    If your app has no Application subclass yet, add one like this and register it with android:name=".MyApplication" on the <application> element in AndroidManifest.xml. configure keeps only the application context and does not wait for the network. Calling it again with the same App ID does nothing.

    Call every Reception member from the main thread; a call from another thread is posted there. Java code reaches the same members as static methods, for example Reception.configure(this, "app_YOUR_APP_ID").

    3. Open chat

    Copy your app’s existing button style and call Reception.openChat():

    kotlin
    import androidx.compose.material3.TextButton
    import androidx.compose.material3.Text
    import sh.reception.Reception
    
    // Inside your composable; use your own button styling.
    TextButton(onClick = { Reception.openChat() }) { Text("Support") }

    In a View-based screen, call it from a click listener: supportButton.setOnClickListener { Reception.openChat() }.

    openChat() shows the chat as a bottom sheet over the current activity. The sheet closes by dragging, tapping outside and Back. Reception.closeChat() dismisses it. Your app owns the Support entry and its unread badge.

    Appearance and language

    Immediately after configuration, set Reception.appearance.accentColor, onAccentColor, chatBackground, incomingBackground, incomingForeground and fontFamily from matching values of your app’s theme. Leave unmatched options unset. Without an accent color, the SDK’s sheet uses the device’s dynamic color on Android 12 and later, and blue below.

    Each color is a ReceptionColor. Pass the light and dark values of your theme as a pair, so the chat follows light and dark mode by itself:

    MyApplication.kt
    // After Reception.configure. LightColorScheme and DarkColorScheme are your theme's color schemes
    // (Theme.kt in Android Studio's template; make them internal instead of private).
    Reception.appearance.accentColor = ReceptionColor(light = LightColorScheme.primary, dark = DarkColorScheme.primary)
    Reception.appearance.onAccentColor = ReceptionColor(light = LightColorScheme.onPrimary, dark = DarkColorScheme.onPrimary)
    Reception.appearance.chatBackground = ReceptionColor(light = LightColorScheme.surface, dark = DarkColorScheme.surface)

    ReceptionColor(color) uses one color in both modes. If your theme’s colors are color resources with a values-night variant, use ReceptionColor.resource(R.color.brand); the chat uses the night variant in dark mode. Java code passes ARGB values: ReceptionColor.argb(0xFF5B4FE0, 0xFFB0A8FF).

    For a fixed or app-selected light/dark mode, set appearance.preferredColorScheme to Reception.ColorScheme.LIGHT or DARK; null follows the system. For a custom font, build a Compose FontFamily from your bundled fonts, for example FontFamily(Font(R.font.inter_regular), Font(R.font.inter_bold, FontWeight.Bold)).

    The chat follows the app’s language: its per-app language when one is set (Android 13+, or AppCompat’s setApplicationLocales), otherwise the phone’s language. On Android 13 and later, declare your app’s languages with android:localeConfig so an English-only app keeps an English chat on a German phone. Only with an in-app language picker of your own, set Reception.languageOverride to its language tag at launch and whenever it changes. Keep the built-in texts until the design round. See Appearance and Language.

    Entry and unread badges

    Reception.unreadCount and Reception.isChatOpen are StateFlows. In Compose, collect the count inside your existing button and hide the badge at zero:

    kotlin
    val unread by Reception.unreadCount.collectAsState()
    BadgedBox(badge = { if (unread > 0) Badge { Text("$unread") } }) {
        // Your existing Support button or icon.
    }

    In a Kotlin View-based screen, collect it while the screen is started:

    kotlin
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            Reception.unreadCount.collect { unread ->
                badge.isVisible = unread > 0
                badge.text = unread.toString()
            }
        }
    }

    Java code adds a listener instead, which receives the current count at once and every change on the main thread: Reception.addUnreadCountListener(listener) in onStart, Reception.removeUnreadCountListener(listener) in onStop; see Unread messages.

    Apply the app’s badge style and the setup prompt’s placement rules. Badge only the tab with the main entry, for example with BadgedBox on that NavigationBarItem icon, or getOrCreateBadge on a BottomNavigationView item (remove it at zero). More placements: Open & close chat and Unread badges.

    Accounts and user details

    Call these after configuration, using values from your app:

    kotlin
    // A name without an account: userId is null.
    Reception.identify(userId = userId, name = name, email = email)
    Reception.setMetadata(metadata) // Complete Map<String, String>, at most 20 fields.

    Use null for unavailable identity fields. Send entered names even in apps without accounts. Update identity and the complete metadata map whenever they change; names and emails belong in identify, never metadata.

    When an actual signed-in session ends (including an expired restored session or account switch), call Reception.logout() before the next identity. Never log out a guest at startup. For deletion, call Reception.deleteData() from a coroutine before discarding the session; catch ReceptionError, keep the session and offer retry on failure:

    kotlin
    lifecycleScope.launch {
        try {
            Reception.deleteData()
            // Continue your account-deletion flow.
        } catch (error: ReceptionError) {
            // Keep the session; show your error UI and offer retry.
        }
    }

    Java and callback code uses Reception.deleteData { error -> … }, which receives null on success. Both logout() and deleteData() clear identity, metadata and push token; supply them again for the next session. The SDK registers the push token again by itself.

    For verified identity, use Reception.identifyWithToken(token) with your server’s signed token. Handle ReceptionEvent.IdentityRejected in Reception.onEvent through the app’s token-refresh flow. Follow Users & accounts for the server setup, the Android token example, refresh, account switching and deletion limits.

    Push

    Reception delivers Android notifications through Firebase Cloud Messaging (FCM). Your app needs Firebase Messaging; the SDK does the rest. Wire push even if permission has not been granted, preserving the app’s existing consent flow. Never request permission here.

    If your app does not use Firebase yet, add your Android app to a Firebase project (Firebase console → Project settings → Add app, with the app’s applicationId), download google-services.json into the app module, and add the Google services plugin and Firebase Messaging:

    app/build.gradle.kts
    plugins {
        // Keep your existing plugins.
        id("com.google.gms.google-services")
    }
    
    dependencies {
        implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
        implementation("com.google.firebase:firebase-messaging")
    }

    Declare the plugin in the root build.gradle.kts with id("com.google.gms.google-services") version "4.4.2" apply false. Then let the SDK’s ready-made service receive the messages; add this inside <application> in AndroidManifest.xml:

    AndroidManifest.xml
    <service
        android:name="sh.reception.ReceptionMessagingService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>

    If your app already has a FirebaseMessagingService, do not add the service above: only one service receives FCM messages. Forward Reception’s messages from your own service instead:

    kotlin
    class MyMessagingService : FirebaseMessagingService() {
        override fun onMessageReceived(message: RemoteMessage) {
            if (Reception.handlePushMessage(this, message.data, message.messageId)) return
            // Keep your existing handling for other messages here.
        }
    
        override fun onNewToken(token: String) {
            Reception.setPushToken(token)
            // Keep your existing token handling here.
        }
    }

    handlePushMessage shows Reception’s notification (none while the chat is open), refreshes the unread count and returns true; other messages return false and stay yours. It may be called from FCM’s worker thread. A message that FCM delivers twice shows one notification: the SDK recognizes the repeated FCM message ID, which is why you pass message.messageId (Firebase removes it from message.data).

    With Firebase in the app, the SDK fetches the FCM token and registers it at configure and every time the app returns to the foreground, when notifications are allowed. Call Reception.registerForPushIfAuthorized() after your existing permission flow succeeds; it never asks for permission. Forward the FCM token, never an APNs token or OneSignal ID. Taps on Reception’s notifications bring back your app as the person left it, or start it, and open the chat, with no code in your app.

    The SDK posts its notifications on its own channel, “Support replies”, in the person’s language. Set Reception.notificationSmallIcon to your app’s monochrome notification icon (for example R.drawable.ic_notification) at launch; without it, the SDK draws a bell. If the app sets its own launcher badge counts, set Reception.updatesAppBadge = false at launch.

    Upload your Firebase service-account key in Push notifications, then verify delivery on a device or emulator with Google Play. With Ask for push notifications on in the dashboard, the SDK can also ask through a card in the chat on Android 13 and later. The card appears only while remote appearance is on (the default); with Reception.usesRemoteAppearance = false the chat shows no card. See Push on Android for receipt, taps and badges in detail.

    Paywalls

    Register your app’s purchase screens and present them from Reception.onPaywall. The sheet is its own activity, so a paywall your app shows as a dialog or another activity appears under it: close the chat first.

    kotlin
    Reception.paywalls = listOf(ReceptionPaywall(id = "plans", title = "Plans"))
    Reception.onPaywall = { id ->
        Reception.closeChat() // The paywall then shows above, not under, the chat.
        showYourPaywall(id)
    }

    showYourPaywall is your app’s function. An embedded ReceptionChatView is part of your screen and needs no closeChat(). See Paywall cards on Android for availability checks and subscription changes.

    Screens and links

    Link cards open websites, other apps, Google Play, mail and the phone without code. To let them open screens of your app, list the screens once after configuration:

    kotlin
    Reception.screens = listOf(
        ReceptionScreen("settings") { navController.navigate("settings") },
        ReceptionScreen("order", value = "Order number") { number -> navController.navigate("order/$number") },
    )

    The SDK closes its sheet, then runs the screen on the main thread. Web links open in the app that claims them, otherwise in a Custom Tab over the chat; set Reception.opensWebLinksInApp = false to hand them to the browser. See Link cards.

    Automations

    Report a moment for automations with Reception.track("paywall_declined", opensChat = true). It returns at once and does nothing offline or without a listening automation. With Google Play Billing in the app, the SDK reports whether the person has an active purchase; see Subscription states.

    Embedded chat

    Place ReceptionChatView() in your own screen after configuration. Your screen owns dismissal; do not also call openChat():

    kotlin
    import sh.reception.ReceptionChatView
    
    // In your own destination, under your app's MaterialTheme.
    Scaffold(topBar = { /* Your existing top bar. */ }) { padding ->
        // The chat applies the keyboard and navigation-bar insets itself.
        ReceptionChatView(Modifier.fillMaxSize().padding(top = padding.calculateTopPadding()))
    }

    The embedded chat follows the enclosing MaterialTheme: its primary color is the accent and its background decides light or dark mode, unless you set those options. It keeps its composer above the keyboard and the navigation bar in edge-to-edge activities (enableEdgeToEdge(), the default from Android 15), so pass only the top padding of your Scaffold. View-based screens use the sh.reception.ReceptionChatView View, in XML or with ReceptionChatView(context). See Custom navigation.

    Events and diagnostics

    Assign Reception.onEvent for chat, message, image, review, paywall, link, deletion and identity-rejection events:

    kotlin
    Reception.onEvent = { event ->
        when (event) {
            is ReceptionEvent.IdentityRejected ->
                if (event.error.code == "identity_token_expired") {
                    // Fetch a fresh token for the same signed-in account.
                }
            else -> Unit // Your existing analytics, if any.
        }
    }

    Set Reception.logLevel = Reception.LogLevel.DEBUG while diagnosing an integration; the SDK logs to Logcat with the tag Reception. See the Kotlin API reference for the complete API.

    Build and emulator checks

    Build and install a debug build on an emulator or a connected phone, for example with ./gradlew :app:installDebug. Then run the setup check: on a debuggable build, launching the app with the extra ReceptionSetupCheck sends “Hello from setup” once and logs the result. Replace the package and launcher activity with your app’s:

    Terminal
    adb shell am start -S -n com.example.app/.MainActivity --es ReceptionSetupCheck app_YOUR_APP_ID
    adb logcat -d -s Reception | grep "Setup check"

    Repeat the log command until Setup check passed or Setup check failed: <reason> appears. The reasons are the same as on iOS: app_id_mismatch, send_not_allowed, not_delivered, timeout and app_not_active. The check needs configure in Application.onCreate. The -ReceptionPreview screens of the iOS SDK are not available on Android.

    Updating

    Raise the version in the dependency, sync Gradle and rebuild. The Android SDK has its own version numbers; Reception.version reports it. The changelog lists what each release changes.