Android
Native Kotlin chat SDK with a full Jetpack Compose UI: AI chatbot, live-agent handoff, attachments, typing indicators and unread counts.
Installlink
mavenCentral() must be present in dependencyResolutionManagement; consumer R8/ProGuard rules ship in the AAR. minSdk 24, JDK 17.
implementation("cloud.newinstance:liveandaichat:0.5.0")Quick startlink
Your backend mints a customer token (see Session tokens (REST API)) and hands it to the app:
val chat = LiveAndAiChat.Builder(context)
.config(LiveAndAiChatConfig(customerToken = tokenFromYourBackend))
.build()
chat.openChat()openChat() starts the bundled chat activity internally; Compose hosts can embed ChatScreen(sdk, onClose, onPickFile = {}, modifier) directly.
Auth modeslink
Configure exactly one credential. Both, neither, or a bare key ID make LiveAndAiChatConfig throw LiveAndAiChatError with code E_CONFIG. Its toString() redacts the secret and the token.
Customer token mode (recommended). LiveAndAiChatConfig(customerToken = ...). The SDK sends Authorization: Bearer <token> and the server derives the customer from the signed claims, so setUser fields are not sent. When a fresh token is due, call chat.setUserToken(freshTokenFromYourBackend): it swaps the credential for every later request and reconnects the realtime subscription without destroying the SDK. A token for the same customer keeps the conversation; the SDK compares the token's subject, not the token string.
Key mode (trusted internal apps only). This ships your secret inside the APK, so never use it in an app distributed to the public.
val chat = LiveAndAiChat.Builder(context)
.config(LiveAndAiChatConfig(apiKey = "sk_live_abc123:YOUR_SECRET_KEY"))
.build()
chat.setUser(ChatUser(customerName = "Ada", customerEmail = "ada@example.com", customerId = "usr_123"))The SDK sends the full key in x-api-key. ChatUser requires a non-blank customerName (it throws otherwise); switching to a different customer in setUser clears the saved conversation and starts fresh. setUserToken throws E_CONFIG in key mode.
Errorslink
LiveAndAiChat.state exposes a ChatSdkError with a stable code, and listeners receive onError(LiveAndAiChatError) carrying the same code. Codes are shared with every other chat SDK; see Authentication and identity -> Diagnosing problems. An auth failure is reported as INVALID_IDENTITY_TOKEN in customer token mode and INVALID_PUBLIC_KEY in key mode, which is the difference between minting a fresh token and fixing the key. After an auth failure the SDK does not reconnect with the same credential; a later setUserToken restarts it.
Configurationlink
| Field | Default | Notes |
|---|---|---|
customerToken | none | Customer token minted by your backend. Selects customer token mode (recommended) |
apiKey | none | The full keyId:secret, sent whole. Selects key mode, trusted internal apps only. A bare key ID is rejected with E_CONFIG |
baseUrl | https://service.newinstance.cloud | Release builds throw on any override (debug-only escape hatch) |
gqlPath / ssePath / wsPath | /service / /graphql/stream / /graphql/ws | Transport paths |
transport | inherited | sse or ws override |
initialMessage | none | Message sent automatically when the conversation opens |
theme | dashboard theme | ChatThemeOverride(...) local theme, ranked below the dashboard theme and above the built-in one. See Theming |
allowRemoteAttachmentUrls | false | Opt-in: lets attach() download and upload http(s) sources |
API surfacelink
initialize, openChat, closeChat, sendMessage, retryMessage, attachFile(bytes, name, mimeType, previewUri), attach(AttachmentRequest, previewUri?), removeAttachment (cancels an in-flight upload), clearAttachments, updateDraft, requestHandoff(reason), connectToAgent(reason), sendTypingStart / sendTypingStop, setUser, setUserToken, addListener / removeListener, destroy.
Observe state via StateFlows: lifecycle, connectionState, messages, conversation, assignment, agentTyping, unreadCount, orgConfig, agentsOnline, attachments, flowState, widgetOpen, draftText; or a listener with onMessageReceived, onMessageSent, onTyping, onConnectionChange, onUnreadCountChange, onError, onAttachmentUpdate, onChatClosed. Errors carry type (NETWORK, VALIDATION, AUTH, SYSTEM) and recoverable.
Programmatic attachmentslink
attach(AttachmentRequest(source, name, mimeType?, declaredSize?, metadata)) accepts any source: AttachmentSource.Base64Data, DataUri, FilePath, ContentUri, Bytes, or RemoteUrl (config-gated). AttachmentSource.detect(value) safely classifies strings. Validation runs before anything is queued: base64 must decode, URIs must be readable, files must be non-empty and at most 25 MB, MIME must be png/jpeg/webp/gif/pdf, a wrong declaredSize is rejected as corruption, and the extension must agree with the MIME type. Reads and uploads run off the main thread with real byte progress on the attachments flow and onAttachmentUpdate (statuses QUEUED, UPLOADING, UPLOADED, FAILED, CANCELLED). A message is never sent with an attachment that has not finished uploading, and attachment contents are never logged.
The chat close eventlink
onChatClosed fires exactly once per chat presentation, whichever way the screen goes away: the in-chat X (close_button), the system back button or gesture (back_navigation), closeChat() (programmatic; it now also finishes the chat activity), destroy() (session_ended), or host navigation (host_navigation). The event carries reason, initiator (user/host/sdk), timestamp, conversationId, assignmentId, channel, previousState, unreadCount, hasDraft, pendingAttachmentCount and SDK metadata; never message contents, credentials or attachment data.
Platform noteslink
- The SDK manifest already declares INTERNET, ACCESS_NETWORK_STATE and legacy WRITE_EXTERNAL_STORAGE (maxSdkVersion 28) and merges the chat activity; add READ_MEDIA_IMAGES only if your own picker needs it.
- Theming comes from the org configuration (the dashboard can force dark or light; otherwise the system theme applies); there is no local theme parameter.
- All UI strings are Android string resources, so hosts can localise with
values-xxoverrides. - No push notifications: unread is in-app only via
unreadCount. destroy()cancels the SDK scope; any later call throws IllegalStateException, so build a new instance to start again.- Steps from your connected MCP servers (forms and secure pages) require 0.5.0; see MCP servers.