Kindly Android SDK — Events Reference
Deep reference for KindlyEvent and every payload type in the Android SDK event system. Companion to SKILL.md — use this when an agent needs to read or react to fields beyond what SKILL.md covers.
All types listed here live in no.kindly.chatsdk.chat.models and are public Kotlin data classes / sealed classes.
Subscribing
Two Kotlin Flows are exposed:
KindlySDK.events: Flow<KindlyEvent> // all events
KindlySDK.state: Flow<KindlyEventState> // state-only convenienceTypical subscription from a CoroutineScope (lifecycleScope, viewModelScope, …):
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import no.kindly.chatsdk.chat.models.*
lifecycleScope.launch {
KindlySDK.events.collect { event ->
when (val detail = event.detail) {
is KindlyEventDetail.Load -> handleLoad(detail.settings)
is KindlyEventDetail.Message -> handleMessage(detail.newMessage, detail.chatLog)
is KindlyEventDetail.ButtonClick -> handleButton(detail.button, detail.buttonType,
detail.chatLog, detail.lastMessage)
is KindlyEventDetail.UI -> handleUI(detail.action)
is KindlyEventDetail.GlobalIcon -> handleGlobalIcon(detail.action)
is KindlyEventDetail.State -> handleState(detail.state)
}
}
}KindlySDK.events is a MutableSharedFlow with replay = 10, so a late subscriber receives the last 10 events.
Event Types at a Glance
KindlyEventType | value | detail subclass | Payload |
|---|---|---|---|
LOAD | "kindly:load" | KindlyEventDetail.Load | KindlyEventSettings |
MESSAGE | "kindly:message" | KindlyEventDetail.Message | KindlyEventMessage + List<KindlyEventMessage> |
BUTTON_CLICK | "kindly:buttonclick" | KindlyEventDetail.ButtonClick | KindlyEventButton + String + List<KindlyEventMessage> + KindlyEventMessage |
UI | "kindly:ui" | KindlyEventDetail.UI | KindlyEventUIAction |
GLOBAL_ICON | "kindly:globalicon" | KindlyEventDetail.GlobalIcon | KindlyEventGlobalIcon — defined but not emitted on mobile |
STATE | "kindly:state" | KindlyEventDetail.State | KindlyEventState |
KindlyEvent
data class KindlyEvent(
val type: KindlyEventType,
val detail: KindlyEventDetail,
val timestamp: Date = Date(),
)KindlyEventDetail
sealed class KindlyEventDetail {
data class Load(val settings: KindlyEventSettings) : KindlyEventDetail()
data class Message(
val newMessage: KindlyEventMessage,
val chatLog: List<KindlyEventMessage>,
) : KindlyEventDetail()
data class ButtonClick(
val button: KindlyEventButton,
val buttonType: String,
val chatLog: List<KindlyEventMessage>,
val lastMessage: KindlyEventMessage,
) : KindlyEventDetail()
data class UI(val action: KindlyEventUIAction) : KindlyEventDetail()
data class GlobalIcon(val action: KindlyEventGlobalIcon) : KindlyEventDetail()
data class State(val state: KindlyEventState) : KindlyEventDetail()
}Payloads
KindlyEventSettings — KindlyEventDetail.Load
Configuration loaded from the backend after start() connects.
Field | Type | Notes |
|---|---|---|
name | String | Bot / workspace name |
id | String | Settings ID |
languages | List<KindlyEventLanguage> | Languages the bot supports |
style | KindlyEventStyle | API-provided theme (hex strings) |
KindlyEventMessage — inside Message / ButtonClick / chatLog
data class KindlyEventMessage(
val id: String,
val text: String? = null,
val sender: KindlyEventMessageSender,
val created: Date? = null,
val isFromUser: Boolean,
val exchangeID: String? = null,
val videoSource: String? = null,
val exchangeType: KindlyEventExchangeType? = null,
val buttons: List<KindlyEventButton> = emptyList(),
val images: List<KindlyEventImage> = emptyList(),
val attachments: List<KindlyEventAttachment> = emptyList(),
)- text is null for messages that carry only buttons, images, or attachments.
- isFromUser is a convenience shortcut for sender == KindlyEventMessageSender.USER.
- exchangeID / exchangeType identify which dialogue node produced the message.
KindlyEventButton — inside ButtonClick / KindlyEventMessage.buttons
data class KindlyEventButton(
val id: String? = null,
val type: String, // e.g. "quickreply", "postback", "url"
val label: String, // text shown on the button
val value: String? = null, // payload; interpretation depends on `type`
val index: Int? = null, // position in the button list
val hasBeenSelected: Boolean = false,
val context: Map<String, Any>? = null,
)KindlyEventUIAction — KindlyEventDetail.UI
data class KindlyEventUIAction(
val type: KindlyUIActionType,
val source: String = "user", // "user" or "programmatic"
val chatId: String,
val value: Map<String, Any>? = null,
val feedbackType: String? = null, // SHOW/SUBMIT/DISMISS_FEEDBACK only
val feedbackValue: Map<String, Any>? = null,
val languageCode: String? = null, // CHANGE_LANGUAGE only
val languageName: String? = null,
val previousLanguageCode: String? = null,
val previousLanguageName: String? = null,
val nextPage: String? = null,
)Most fields are context-specific — populated only when type requires them (see table below).
KindlyEventGlobalIcon — KindlyEventDetail.GlobalIcon
data class KindlyEventGlobalIcon(
val type: String,
val source: String = "user",
val value: Map<String, Any>? = null,
)Defined for API parity with the web widget. Not currently emitted by the Android SDK.
KindlyEventState — KindlyEventDetail.State
data class KindlyEventState(
val isChatDisplayed: Boolean, // chat UI is on screen
val isStarted: Boolean, // KindlySDK.start(...) completed
val isSocketConnected: Boolean, // Pusher WebSocket is live
val isConnected: Boolean, // isStarted && isSocketConnected
val connectionState: String, // "connected", "connecting", "disconnected", etc.
)Use isConnected as the single "safe to send messages / trigger dialogues" flag.
Supporting Types
KindlyEventLanguage
data class KindlyEventLanguage(
val code: String, // "en", "sv", "fr", ...
val name: String, // "English", "Swedish", ...
val isDefault: Boolean = false,
)KindlyEventStyle
API-provided theme values (hex strings). All optional — fields are null when the backend hasn't configured a value.
Field | Type |
|---|---|
background | String? |
buttonBackground | String? |
buttonText | String? |
headerBackground | String? |
headerText | String? |
inputBackground | String? |
inputText | String? |
userMessageBackground | String? |
userMessageTextColor | String? |
botMessageBackground | String? |
botMessageTextColor | String? |
KindlyEventImage
data class KindlyEventImage(
val url: String,
val altText: String? = null,
val id: String? = null,
)KindlyEventAttachment
data class KindlyEventAttachment(
val id: String,
val name: String,
val format: String, // e.g. "pdf", "png"
val status: String, // e.g. "uploaded", "pending"
val sender: String,
val storagePath: String, // remote storage path
)KindlyEventMessageSender (enum)
Case | value |
|---|---|
AGENT | "AGENT" |
BOT | "BOT" |
SYSTEM | "SYSTEM" |
USER | "USER" |
UNKNOWN | "UNKNOWN" |
KindlyEventExchangeType (enum)
Case | value |
|---|---|
DIALOGUE | "dialogue" |
FALLBACK | "fallback" |
GREETING | "greeting" |
SYSTEM_DIALOGUE | "system_dialogue" |
USER_SAYS | "user_says" |
KindlyUIActionType (enum)
Full list of UI actions emitted via KindlyEventDetail.UI:
Case | value | When it fires | Extra fields you can expect |
|---|---|---|---|
OPEN_CHAT_BUBBLE | "open-chatbubble" | Chat UI presented | — |
CLOSE_CHAT_BUBBLE | "close-chatbubble" | Chat UI dismissed | — |
END_CHAT | "end-chat" | Session ended | — |
RESTART_CHAT | "restart-chat" | User restarted the session | — |
DOWNLOAD_CHAT | "download-chat" | User downloaded the transcript | — |
DELETE_CHAT | "delete-chat" | Chat history cleared | — |
CHANGE_LANGUAGE | "change-language" | Language switched | languageCode, languageName, previousLanguageCode, previousLanguageName |
SHOW_FEEDBACK | "show-feedback" | Feedback prompt shown | feedbackType |
SUBMIT_FEEDBACK | "submit-feedback" | Feedback submitted | feedbackType, feedbackValue |
DISMISS_FEEDBACK | "dismiss-feedback" | Feedback dismissed | feedbackType |
Worked Examples
React to a bot reply only
lifecycleScope.launch {
KindlySDK.events
.mapNotNull { event ->
(event.detail as? KindlyEventDetail.Message)
?.takeIf { it.newMessage.sender == KindlyEventMessageSender.BOT }
?.newMessage
}
.collect { botMessage ->
Log.d("Kindly", "Bot: ${botMessage.text.orEmpty()}")
}
}Log every button the user taps
lifecycleScope.launch {
KindlySDK.events.collect { event ->
val click = event.detail as? KindlyEventDetail.ButtonClick ?: return@collect
Log.d("Kindly",
"User tapped [${click.buttonType}] ${click.button.label} → value=${click.button.value}"
)
}
}Track language switches
lifecycleScope.launch {
KindlySDK.events
.mapNotNull { (it.detail as? KindlyEventDetail.UI)?.action }
.filter { it.type == KindlyUIActionType.CHANGE_LANGUAGE }
.collect { action ->
val from = action.previousLanguageCode ?: return@collect
val to = action.languageCode ?: return@collect
analytics.log("language_changed", mapOf("from" to from, "to" to to))
}
}Wait for connection, then trigger a dialogue
lifecycleScope.launch {
KindlySDK.state
.filter { it.isConnected }
.first()
KindlySDK.triggerDialogue(id = "welcome_flow")
}Collect the full chat transcript on demand
lifecycleScope.launch {
KindlySDK.events
.mapNotNull { (it.detail as? KindlyEventDetail.Message)?.chatLog }
.collect { transcript ->
currentTranscript = transcript
}
}Threading Notes
- Both flows emit on whichever dispatcher the SDK processes the event from.
- For UI work, switch dispatchers explicitly with .flowOn(Dispatchers.Main) or collect from lifecycleScope (already main).
lifecycleScope.launch {
KindlySDK.state.collect { state ->
statusTextView.text = state.connectionState
}
}