#Android SDK
A Kotlin library with no UI. It keeps the conversation — messages, typing,
connection — as StateFlows, and you draw it however your app looks, in
Compose or Views. Android 8.0 (API 26) or later; its only dependencies are
OkHttp and kotlinx.coroutines.
#Setup
Create a publishable token that allows your app. Its origin is
android-app:// plus its package name:
vatio tokens create --env live --label android --origin android-app://com.acme.app
To add it to an existing token instead, use vatio tokens origins PREFIX URL...
or the tokens section of Channels → Web widget in the inbox.
The library is built by JitPack from github.com/vatio-ai/android. Add the repository and the dependency:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}
// app/build.gradle.kts
dependencies {
implementation("com.github.vatio-ai:android:0.2.1")
}
Then create a client with the vatpub_ token. It is meant to ship inside the
app; a vat_ token must never.
import ai.vatio.Vatio
import ai.vatio.VatioChat
val vatio = Vatio(context, workspace = "acme", token = "vatpub_REPLACE_ME")
val chat = VatioChat(vatio)
#Using it
chat.messages // StateFlow<List<VatioMessage>>, oldest first: id, role, content, createdAt, isPending
chat.isTyping // the agent is writing
chat.status // IDLE, CONNECTING, CONNECTED, RECONNECTING, CLOSED
chat.send("What does Acme sell?") // suspends
Collect the flows (collectAsStateWithLifecycle() in Compose) and the UI
redraws as the reply arrives. Creating a chat opens nothing: the conversation
starts on the first send, and the visitor's message is in messages at once,
with isPending set until the server confirms it. Call chat.resume() when
the screen appears to reopen the visitor's conversation, if they have one.
Keep the chat in a ViewModel and call chat.close() from onCleared().
A common layout is a text bar at the bottom of a screen that, on submit, switches to a conversation tab and sends:
AskBar { question ->
tab = Tab.Conversation
viewModelScope.launch { chat.send(question) }
}
The library README has the complete Compose example.
| Method | Purpose |
|---|---|
vatio.config() | Agent name, avatar, accent color, greeting and suggestions |
vatio.conversations(visitorToken) | This visitor's conversations on this device, newest first |
chat.send(text, attachments) | Send a message, optionally with files; starts the conversation on first call |
chat.resume() | Reopen the stored conversation; never starts a new one |
chat.open(conversation) | Switch to a conversation from conversations() |
chat.startOver() | Forget the stored conversation; server history is kept |
chat.rate(rating, comment) | Answer the feedback moment: GOOD, NEUTRAL, BAD |
chat.dismissFeedback() | Decline the feedback moment |
chat.close() | Disconnect |
send throws a VatioException (code, message, status) and removes the
pending message, so put the text back in your field. Failures outside a
send land in chat.lastError.
#Files and voice notes
Attach up to four VatioUploads — images, PDFs, text files or audio, up to
8 MB each, as on the web. With files,
the text may be empty:
chat.send(attachments = listOf(VatioUpload.fromUri(context, pickedUri))) // off the main thread: it reads the file
chat.send(attachments = listOf(VatioUpload.fromFile(recording))) // a MediaRecorder .m4a
Once the server answers, the pending message is replaced by the stored one,
and each VatioAttachment has a url. A voice note is transcribed in the
background: the message reads "Audio" at first, and its content becomes the
transcript a few seconds later, in place; the agent answers after that. When
isMediaLabel is true, content is only a stand-in ("Image") — draw the
attachment instead. The example app has a composer with a photo picker and a
voice recorder.
#Visitor feedback
When a request looks resolved, Vatio offers a feedback moment, at most once
per conversation (see the widget guide).
chat.feedback holds it; show your own prompt while isOpen:
val feedback by chat.feedback.collectAsStateWithLifecycle()
feedback?.takeIf { it.isOpen }?.let {
RatingCard(question = it.question ?: "How did ${it.agentName} do?") { rating, comment ->
scope.launch { chat.rate(rating, comment) }
}
}
question is how the agent asks, written for that conversation, or null
when there is none. It goes back to null when the visitor writes again
without answering.
#Signed-in users
Pass a visitor token your backend signed to say who the user is, as described in Sessions and channels:
val chat = VatioChat(vatio, visitorToken = signedToken)
A different token subject is a different person with their own conversation, so signing out of your app signs out of the chat. An anonymous conversation is kept when the user signs in.
#Delivery and storage
Replies arrive over a WebSocket. When it drops, the SDK reconnects, polls in
the meantime, and fetches what it missed; messages are deduplicated by id.
replyStyle (STREAM by default, PACED, INSTANT) works as on the
web.
The conversation and the visitor id are stored in SharedPreferences, per
workspace and signed-in user. Chat credentials last 12 hours. Rate limits
and message length are the same as on the web.