Skip to content

Repository files navigation

Chatty Android SDK

Native Jetpack Compose chat UI for Chatty — zero WebView, zero compromise.

Drop a fully native, on-brand support chat into any Android app in minutes. Talks directly to the same /api/widget/* backend as the Chatty web widget, and renders every bubble, avatar, and composer with real Compose UI — fast, themeable, and indistinguishable from the rest of your app.

CI Release License: MIT minSdk 24 Kotlin Stars

Install · Quick start · Design gallery · API reference · Example app


Why this SDK

No WebView, anywhere Every bubble, avatar, and the composer are real @Composables — no iframe, no JS bridge, no WebView memory overhead.
Matches your dashboard automatically Fetches the bot's theme and renders with the exact colors, corner radii, and launcher shape chosen in the dashboard — no manual styling.
Two integration shapes A floating ChattyLauncher bubble + dialog, or an embedded ChattyChatScreen inside your own layout.
A real composer, not a stub Full-Unicode emoji picker (search + categories + skin tones, via androidx.emoji2), animated attach menu (camera, gallery, documents, location), and mic-to-text voice notes — built in, not bolted on.
Small dependency footprint OkHttp, Coil, and Jetpack Compose Material3. Nothing else.

Install

Note

Use v1.2.0 or later. v1.0.0–v1.0.2 predate fixes that were needed for the SDK, the example app, and CI to actually build cleanly. Full history in the releases — every tag from v1.0.3 onward is CI-verified green before it ships.

Via JitPack — works today, no account needed

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}
// app/build.gradle.kts
dependencies {
    implementation("com.github.PersonaliAI:chatty-android-sdk:v1.2.0")
}
Via Maven Central (published, but currently lagging behind JitPack)

Live at com.personaliai:chatty-android-sdk, but stuck at 1.0.0 — later releases haven't been pushed through release.yml yet. Use the JitPack coordinate above for the latest fixes until this catches up.

dependencies {
    implementation("com.personaliai:chatty-android-sdk:1.0.0")
}
As a local module (building from source)
// settings.gradle.kts
include(":chatty-sdk")
project(":chatty-sdk").projectDir = file("../chatty-android-sdk/chatty-sdk")
// app/build.gradle.kts
dependencies {
    implementation(project(":chatty-sdk"))
}

Quick start

Find your bot ID in the Chatty dashboard under Embed & Integrate → Android SDK.

Floating launcher (recommended) — a bubble that expands into a full-screen chat dialog, the native equivalent of the web widget's launcher button:

@Composable
fun AppRoot() {
    Box(Modifier.fillMaxSize()) {
        // ...your app content...
        ChattyLauncher(botId = "YOUR_BOT_ID")
    }
}

Embedded full-screen chat — place it directly in your own navigation, e.g. as a "Support" tab:

@Composable
fun SupportScreen() {
    ChattyChatScreen(botId = "YOUR_BOT_ID", modifier = Modifier.fillMaxSize())
}

Design gallery

The SDK ships all 10 Chatty widget designs as Compose color/radius tokens, ported 1:1 from the web widget's globals.css, so a native screen looks like whatever design is chosen in the dashboard rather than one generic look. No configuration required — the SDK fetches the bot's theme and resolves the matching token set automatically, including legacy widget_style IDs from older presets.

Design Accent
minimal #1c1a15
playful #ff8a5c
corporate #1c2e4a
dark-sleek #00e5c7
gradient-glow #a855f7
glassmorphism #8f6ff0
ecommerce #0f9d8c
healthcare-calm #6f9c7d
neubrutalism #ff3d67
luxury-editorial #161412

Font pairing (each web design uses a distinct Google Font) is intentionally out of scope for this release; color, radius, and header/bubble treatment carry most of a design's identity.

API reference

ChattyLauncher

@Composable
fun ChattyLauncher(
    botId: String,
    baseUrl: String = CHATTY_DEFAULT_BASE_URL,
    host: String? = null,
    position: ChattyPosition = ChattyPosition.BOTTOM_END,
    color: Color? = null,
    onVoiceCallPress: (() -> Unit)? = null,
    onNotificationBellPress: (() -> Unit)? = null,
    enableVoiceNotes: Boolean = true,
    enableNotificationBell: Boolean = true,
    enableLocationSharing: Boolean = true,
)
Param Description
botId Required. Your bot's ID from the dashboard.
baseUrl Chatty backend base URL. Defaults to the production API.
host Advisory only — sent to the backend but not used for access control. See Notes.
position Corner the bubble docks to. Default BOTTOM_END.
color Overrides the launcher color. Defaults to the active design's accent color.
onVoiceCallPress Forwarded to ChattyChatScreen's header voice-call button. See Notes.
onNotificationBellPress Forwarded to ChattyChatScreen's header notification bell. See Notes.
enableVoiceNotes Forwarded to ChattyChatScreen. See Permissions.
enableNotificationBell Forwarded to ChattyChatScreen. See Permissions.
enableLocationSharing Forwarded to ChattyChatScreen. See Permissions.

ChattyChatScreen

@Composable
fun ChattyChatScreen(
    botId: String,
    baseUrl: String = CHATTY_DEFAULT_BASE_URL,
    host: String? = null,
    hostKey: String = "app",
    modifier: Modifier = Modifier,
    onMessage: ((ChattyMessage) -> Unit)? = null,
    onVoiceCallPress: (() -> Unit)? = null,
    onNotificationBellPress: (() -> Unit)? = null,
    onClose: (() -> Unit)? = null,
    enableVoiceNotes: Boolean = true,
    enableNotificationBell: Boolean = true,
    enableLocationSharing: Boolean = true,
)
Param Description
botId Required. Your bot's ID from the dashboard.
baseUrl Chatty backend base URL. Defaults to the production API.
host Advisory only — sent to the backend but not used for access control. See Notes.
hostKey Storage key used to namespace the locally persisted conversation.
modifier Standard Compose Modifier for sizing/placement.
onMessage Called for every inbound message — useful for unread badges or analytics.
onVoiceCallPress Header voice-call button tapped. Only shown when the bot's dashboard has voice enabled. See Notes.
onNotificationBellPress Header notification-bell button tapped (only shown once POST_NOTIFICATIONS is already granted). See Notes.
onClose Renders a close (✕) button in the header when set. ChattyLauncher passes this for you; set it yourself only if you're embedding ChattyChatScreen directly inside your own dialog/sheet.
enableVoiceNotes Default true. Gates the composer's mic button on RECORD_AUDIO already being granted — set false to hide it regardless of permission state. See Permissions.
enableNotificationBell Default true. Gates the header's bell button on POST_NOTIFICATIONS already being granted — set false to hide it regardless of permission state. See Permissions.
enableLocationSharing Default true. Gates the attach menu's Location option on ACCESS_COARSE_LOCATION already being granted — set false to hide it regardless of permission state. See Permissions.

Notes

Permissions — what this SDK declares, and how to opt out

The SDK's own AndroidManifest.xml declares three dangerous/runtime-gated permissions, all of which get merged into your app's manifest automatically by the Android Gradle Plugin. Camera and Photo Library/Documents aren't in this table — they need no permission at all (system camera intent, Storage Access Framework pickers):

Permission Risk Used for Button shown when
RECORD_AUDIO Dangerous Composer mic button → voice-note transcription Already granted, and enableVoiceNotes (default true)
POST_NOTIFICATIONS Runtime-gated (Android 13+) Header bell button → onNotificationBellPress Already granted (or pre-13, where it's implicit), and enableNotificationBell (default true)
ACCESS_COARSE_LOCATION Dangerous Attach menu's Location option → drops a Google Maps link into the composer text Already granted, and enableLocationSharing (default true)

This SDK never calls the OS permission dialog itself, for any of these three. Each button/menu option above simply doesn't render until its permission is already granted — no silently-failing button, and no system dialog popping up from code your app doesn't own the timing of. Your app is responsible for actually requesting RECORD_AUDIO/POST_NOTIFICATIONS/ACCESS_COARSE_LOCATION (its own rememberLauncherForActivityResult(RequestPermission()), wherever and whenever fits your own onboarding/permission-priming flow); once granted, the corresponding button appears automatically (re-checked whenever the screen resumes — including right after your app's own permission dialog resolves). Set the matching enable* param to false if you don't want the button to ever appear, independent of permission state.

If you also need the permission gone from your app's own merged manifest (e.g. for a Play Store Data Safety form, or a security review that flags any declared dangerous permission), add an explicit removal to your app's AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
</manifest>

Do this only alongside the matching enable* param set to false (e.g. enableVoiceNotes = false for RECORD_AUDIO) — removing a manifest permission while still showing its button leaves a dead button whose permission request will always fail.

The voice-call feature (ChattyVoiceCallScreen, opt-in, requires LiveKit) needs its own RECORD_AUDIO request — see Voice-call button below; it's independent of the composer mic button and isn't controlled by enableVoiceNotes.

Security — bot_id and domain restriction

bot_id is not a secret — it's extractable from any client, web or mobile. Domain restriction (allowed_domains in the dashboard) is enforced by the backend as a rate-limit tier, not a hard reject: verified web traffic gets 30 msgs/60s per bot+IP, everything else (including all mobile SDK traffic — there's no way for a native app to obtain a "verified" token the way a browser's Referer allows) gets throttled to 5 msgs/120s. The host param this SDK sends is advisory only and isn't used for access control. If your bot is mobile-primary, leave allowed_domains empty to get the normal 30/60s tier instead.

Notification bell — what it does and doesn't do

Only rendered once POST_NOTIFICATIONS is already granted (Android 13+ only — older versions grant it at install time, so the bell always shows there); tapping it just calls onNotificationBellPress — the SDK never requests the permission itself, see Permissions. That's as far as this SDK goes. Actually delivering a push when a reply arrives while the app is backgrounded needs a push provider wired up at the app level — either Firebase Cloud Messaging directly (free, no third party) or a wrapper like OneSignal (adds a dashboard/API for managing sends, at the cost of another vendor). Either way it's the same shape of work: register the device's push token, send it to your own backend, store it against the session/user, and have your backend call FCM/OneSignal's send API when a new assistant/agent message lands for a session that isn't actively polling. None of that exists yet — it's backend work in chatty-backend, not something this client SDK can add on its own.

Voice-call button

Only shown when the bot's dashboard has voice enabled, and fires onVoiceCallPress. This SDK now ships a ready-to-render call screen, ChattyVoiceCallScreen — render it yourself from that callback (it's opt-in: only apps that use it need LiveKit's Android SDK pulled in):

// build.gradle.kts (your app module)
dependencies {
    implementation("io.livekit:livekit-android:2.18.2") // or newer
}
// Application.onCreate(), once:
LiveKit.init(applicationContext)
var showCall by remember { mutableStateOf(false) }
if (showCall) {
    ChattyVoiceCallScreen(
        client = client,
        sessionId = sessionId, // the same session id ChattyChatScreen/ChattyViewModel is using
        widgetStyle = state.theme?.widgetStyle,
        onClose = { showCall = false },
    )
} else {
    ChattyChatScreen(state = state, onVoiceCallPress = { showCall = true }, /* ... */)
}

Also add <uses-permission android:name="android.permission.RECORD_AUDIO" /> to your AndroidManifest.xml and request it at runtime before the call screen is shown.

  • Lead capture and meeting booking happen conversationally (the assistant decides to ask/act) — there's no separate REST call to trigger them from the SDK.
  • Polling for human-agent takeover messages runs every 4s while ChattyChatScreen is composed, matching the web widget's behavior.
  • Conversation history is persisted locally (SharedPreferences), mirroring the web widget's localStorage cache, so a returning user sees their prior messages.

Example app

chatty-example-app/ is a minimal, runnable Compose app demonstrating both integration styles side by side — open it in Android Studio, hit run, and try the floating launcher and the embedded full-screen chat against a live demo bot. It's its own Gradle root (built by Android Studio's project wizard, so it starts from current, known-compatible Gradle/AGP/JDK defaults) that pulls in :chatty-sdk as a sibling-directory source dependency — open chatty-example-app/, not the repo root, in Android Studio.

cd chatty-example-app && ./gradlew :app:installDebug

Requirements

  • minSdk 24+

  • Kotlin, Jetpack Compose (Material3)

  • OkHttp, Coil (image loading) — pulled in automatically as transitive dependencies

  • Core library desugaring enabled in your app module — the SDK uses java.time APIs desugared down to minSdk 24, and the Android Gradle Plugin enforces that any consumer of an AAR built this way opts in too:

    // app/build.gradle.kts
    android {
        compileOptions {
            isCoreLibraryDesugaringEnabled = true
        }
    }
    dependencies {
        coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.3")
    }

Contributing — bug reports, design-parity fixes, and PRs are welcome.

Licensed under MIT © PersonaliAI

About

Official Android SDK for Chatty AI chatbots — native Kotlin + Jetpack Compose, no WebView

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages