Android
Add Kustomer Chat to an Android app with the Kotlin SDK. Gradle setup, Kustomer.init, opening the chat UI, the headless KustomerCore API and KusChatListener, identity, attachments, theming, localization, and R8.
The Android SDK is two Maven artifacts. ui gives you the full chat and knowledge base experience as an Activity; core is the headless layer it's built on. Depending on ui brings in core.
Sample apps: kustomer/android-chat-sdk-samples.
Requirements
Install
Add the dependency
The artifacts are on Maven Central:
settings.gradle.ktsdependencyResolutionManagement { repositories { google() mavenCentral() } }app/build.gradle.ktsdependencies { implementation("com.kustomer.chat:ui:4.6.2") }Enable desugaring on minSdk 24 and 25
Only apps with a minSdk below 26 need this:
app/build.gradle.ktsandroid { compileOptions { isCoreLibraryDesugaringEnabled = true } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") }Get a chat key
Create an API key in Settings › Security › API Keys with only the
org.trackingrole and no expiry. The SDK rejects keys with any other role.Initialize in your Application class
MyApplication.ktclass MyApplication : Application() { override fun onCreate() { super.onCreate() val options = KustomerOptions.Builder() .setBrandId("YOUR_BRAND_ID") .setLogLevel(KusLogLevel.Error) .build() try { Kustomer.init(application = this, apiKey = BuildConfig.KUSTOMER_CHAT_KEY, options = options) { result -> Log.i("Kustomer", "Initialized: ${result.dataOrNull}") } } catch (e: AssertionError) { Log.e("Kustomer", "Invalid chat key", e) } } }Initialize in
Application.onCreate(), not in an Activity, so the SDK is ready when Android restarts your process. An invalid key throwsAssertionError. CallingKustomer.getInstance()beforeinitthrowsKusNotInitializedException.Open chat
KotlinKustomer.getInstance().open()
Options
KustomerOptions.Builder() methods all return the builder:
setBrandId(String)String- Which brand's chat settings and knowledge base to use. Defaults to the org's default brand.
setBusinessScheduleId(String)String- Business schedule used for availability.
setChatAssistant(KusActiveAssistant)KusActiveAssistantKusActiveAssistant.WithId("id")for a specific assistant, orKusActiveAssistant.Nonefor none. Defaults to the org's assistant.setUserLocale(Locale)Locale- Force the SDK's language for UI strings and knowledge base content. Use
Locale.forLanguageTag("fr-CA"). useAppLocales(Boolean)Boolean- Follow Android 13+ per-app language settings. Takes precedence over
setUserLocale. hideNewConversationButton(Boolean)Boolean- Hide New conversation.
hideHistoryNavigation(Boolean)Boolean- Hide the back button to the conversation list.
hideKbArticleShare(Boolean)Boolean- Hide the share option on knowledge base articles.
showInAppNotifications(Boolean)Boolean- In-app banners for new messages while your app is in the foreground. Default
true. setShowAllBrandsConversationHistory(Boolean)Boolean- Show conversations from every brand in history, not just this brand's.
setEnableUrlUnfurling(Boolean)Boolean- Link previews in messages. Default
true. setEnableHistoryStatusFilter(Boolean)Boolean- Show a status filter on the history screen.
setHistoryStatusFilter(KusHistoryStatusFilter)enum- Initial filter:
ALL(default),OPEN,CLOSEDorREQUIRES_ATTENTION(shown as Unread). setLogLevel(KusLogLevel)KusLogLevelError(default),Info,DebugorAll.
You can change some of these at runtime on Kustomer.getInstance(): overrideBrand(brandId), overrideBusinessSchedule(scheduleId), changeActiveAssistant(activeAssistant), setHistoryStatusFilter(filter).
Open the UI
All of these are on Kustomer.getInstance(). Callbacks run on the main thread.
val kustomer = Kustomer.getInstance()
kustomer.open() // follows the brand's widget setting
kustomer.open(KusPreferredView.CHAT_ONLY)
kustomer.open(KusPreferredView.KB_ONLY)
kustomer.startNewConversation(
initialMessage = KusInitialMessage("Where is my order?", KusChatMessageDirection.CUSTOMER),
describeAttributes = KusDescribeAttributes(hashMapOf("orderNumberStr" to "4417")),
title = "Order 4417",
) { result ->
if (result is KusResult.Success) Log.i("Kustomer", "Conversation ${result.data.id}")
}
kustomer.openConversationWithId("6716a3a9c4e5f60012ab34c1") { }
kustomer.openChatAssistant(assistantId = "YOUR_ASSISTANT_ID")
kustomer.openKbArticle("ARTICLE_ID") { }
kustomer.openKbCategory("CATEGORY_ID") { }
kustomer.close() // closes the UI; doesn't log out
kustomer.isVisible()About startNewConversation:
- A
CUSTOMERinitial message is sent right away and creates the conversation. AnAGENTone is shown as a greeting, and the conversation is created when the customer replies. Assistants ignoreAGENTinitial messages. titleis capped at 256 characters. Without one, the first message is used.KusDescribeAttributessets custom attributes on the new conversation, using attribute names such asorderNumberStr.
openConversationWithId follows merges: if the conversation was merged, it opens the one it was merged into. The chat UI runs in its own portrait Activity.
Unread counts and availability
Kustomer.getInstance().observeUnreadCount().observe(this) { count ->
badge.isVisible = count > 0
badge.text = count.toString()
}
Kustomer.getInstance().isChatAvailable { result ->
val availability = result.dataOrNull // KUS_ONLINE, KUS_OFFLINE, KUS_HIDDEN or KUS_DISABLED
helpButton.isVisible = availability != KusChatAvailability.KUS_HIDDEN && availability != KusChatAvailability.KUS_DISABLED
}observeUnreadCount() counts conversations updated in the last 30 days. observeActiveConversationIds() gives the IDs of open conversations.
Headless: KustomerCore
To build your own UI, use the providers on KustomerCore. With the ui artifact, call Kustomer.init (it initializes Core for you); with only core, call KustomerCore.init(application, apiKey, options).
val chat = KustomerCore.getInstance().kusChatProvider()
lifecycleScope.launch {
when (val created = chat.createConversation(title = "Order 4417", messages = listOf("Where is my order?"), attachments = null)) {
is KusResult.Success -> {
val (conversation, messages) = created.data
chat.sendMessageWithAttachments(body = "It was due Monday.", messageAction = null, attachments = null, conversationId = conversation.id)
}
is KusResult.Error -> Log.e("Kustomer", "Create failed", created.exception)
KusResult.Loading -> Unit
}
}Provider methods are suspend functions that return KusResult<T>: Success(data) or Error(exception). KusResult also has a Loading object, so a when expression needs a branch for it.
The knowledge base is on KustomerCore.getInstance().kusKbProvider(): fetchRootCategory(locale), fetchFeaturedArticles(locale), fetchCategoryById(id, locale), searchArticles(term, page, pageSize, locale) and getArticleById(id, locale, knowledgeBaseId).
Listen for events
Implement KusChatListener and register it after init:
val listener = object : KusChatListener {
override fun onChatMessageReceived(conversationId: String, chatMessage: KusChatMessage) { }
override fun onAgentIsTyping(conversationId: String, typingIndicator: KusTypingIndicator) { }
override fun onUnreadCountChange(conversationId: String, unreadCount: Int) { }
override fun onAgentJoined(conversationId: String, agent: KusUser) { }
override fun onPreviewChanged(conversationId: String, preview: KusConversationPreview) { }
override fun onConversationLastMessageAtChanged(conversationId: String, lastMessageAt: Long) { }
override fun onConversationCreated(conversation: KusConversation) { }
override fun onConversationEnded(conversation: KusConversation) { }
override fun onConversationDeleted(conversation: KusConversation) { }
override fun onConversationUnended(conversation: KusConversation) { }
override fun onCustomerMerged(customerId: String) { }
override fun onCustomerDeleted() { }
override fun onSatisfactionEventReceived(conversationId: String, satisfaction: KusSatisfaction) { }
override fun onAssistantEnded(conversation: KusConversation) { }
override fun onConversationMerged(source: KusConversation, target: KusConversation) { }
}
Kustomer.getInstance().addChatListener(listener)onChatMessageReceived fires for the customer's own messages too; check chatMessage.direction. onAgentJoined fires on the first agent reply, not on assignment. onSessionOpened, onSessionTitleChanged, onSessionCustomAttributesUpdated and onUrlPreviewReceived have default implementations, so override them only if you need them.
Identity
val kustomer = Kustomer.getInstance()
if (kustomer.isLoggedIn(userEmail = user.email).dataOrNull != true) {
kustomer.logIn(jwtFromYourServer) { result ->
if (result is KusResult.Error) Log.e("Kustomer", "Login failed", result.exception)
}
}
kustomer.describeCustomer(
KusCustomerDescribeAttributes(sharedPhones = listOf(KusPhone("+15555550123")), custom = mapOf("loyaltyTierStr" to "gold"))
) { }
// on sign-out
lifecycleScope.launch {
kustomer.deregisterDeviceForPushNotifications()
kustomer.logOut()
}isLoggedIn(userEmail, userId)is synchronous and compares case-insensitively with the identity in the stored token.- Call
logInafter your user signs in, not on every launch. logOut()clears the SDK's data but doesn't deregister push, so deregister first.
The JWT comes from your server. See Customer identity.
Attachments
Images are re-encoded as JPEG and downscaled to about one megapixel before upload. Admins can turn attachments off in chat settings.
The SDK registers a FileProvider with the authority ${applicationId}.kustomersdk, so don't use that authority yourself. It doesn't declare the CAMERA permission: photos use the system camera app through ACTION_IMAGE_CAPTURE.
Theming
The chat Activity uses the theme KusAppTheme.Overlay, based on Theme.MaterialComponents.DayNight. Override it in your app to apply your colors:
<style name="KusAppTheme.Overlay" parent="KusAppTheme">
<item name="kusColorPrimary">@color/brand</item>
<item name="kusColorOnPrimary">@android:color/white</item>
<item name="kusColorCustomerBubble">@color/brand</item>
<item name="kusColorOnCustomerBubble">@android:color/white</item>
</style>The SDK follows your app's night mode. Put dark values in res/values-night/styles.xml too. Text styles (KusTextAppearance.*) can be overridden the same way.
Localization
The SDK includes translations for more than 100 locales and follows the device language. Force one with setUserLocale, or follow per-app languages with useAppLocales(true). Every UI string is a kus_* resource you can override in your own strings.xml.
R8 and ProGuard
Rules ship inside the AARs and apply automatically, including in R8 full mode.