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.
Install · Quick start · Design gallery · API reference · Example app
| 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 | Emoji picker, animated attach menu (camera + gallery), and mic-to-text voice notes — built in, not bolted on. |
| Small dependency footprint | OkHttp, Coil, and Jetpack Compose Material3. Nothing else. |
Note
Use v1.0.6 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.
// 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.0.6")
}Via Maven Central (configured, publish pending Sonatype verification)
Publishing is fully wired up (com.vanniktech.maven.publish, see
chatty-sdk/build.gradle.kts, and the tag-triggered
release.yml workflow) but not yet live — it needs a verified
Sonatype account for the com.personaliai namespace. Once published:
dependencies {
implementation("com.personaliai:chatty-android-sdk:1.0.6")
}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"))
}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())
}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 |
|
playful |
|
corporate |
|
dark-sleek |
|
gradient-glow |
|
glassmorphism |
|
ecommerce |
|
healthcare-calm |
|
neubrutalism |
|
luxury-editorial |
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.
@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,
)| 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. |
@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,
)| 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, after the OS permission prompt resolves. 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. |
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
Tapping it requests the OS notification permission (Android 13+ only — older versions grant it
at install time) and then calls onNotificationBellPress. 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 only fires onVoiceCallPress — this
SDK doesn't bundle a voice-call implementation (that's a separate LiveKit integration, out of
scope here).
- 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
ChattyChatScreenis composed, matching the web widget's behavior. - Conversation history is persisted locally (
SharedPreferences), mirroring the web widget'slocalStoragecache, so a returning user sees their prior messages.
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.
./gradlew :example-app:installDebug-
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.timeAPIs desugared down tominSdk 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