Kindly Android SDK — Deep Links & Link Interception Reference
Deep reference for two related but distinct features. Companion to SKILL.md.
Feature | Direction | Mechanism |
|---|---|---|
Deep links | External → Kindly | kindly:// URL scheme handled by KindlySDK.handleUrl(context, uri) |
Link interception | Kindly → External | KindlySDKInteraction.shouldHandleLink(url) |
Deep Links: kindly:// URL scheme
The SDK accepts URIs of the form:
kindly://chat → opens the chat UI
kindly://chat/dialogue/{dialogueId} → opens chat and triggers the named dialogue1. Register the intent filter
Add to your launcher (or a dedicated handler) Activity in AndroidManifest.xml:
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="false">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="kindly" android:host="chat" />
</intent-filter>
</activity>2. Hand incoming URIs to the SDK
Activity (XML or Compose host)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent?.data?.let { handleIfKindly(it) }
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { handleIfKindly(it) }
}
private fun handleIfKindly(uri: Uri) {
if (KindlySDK.handleUrl(this, uri)) return
// not a Kindly URL — handle elsewhere
}
}3. Programmatic dialogue trigger (no URL needed)
If the trigger is internal (a button in your own UI), skip the URL round-trip:
KindlySDK.triggerDialogue(id = "welcome_flow")Requires an active connection (check KindlySDK.state first if unsure — see EVENTS.md).
handleUrl(context:, uri:) reference
public fun handleUrl(context: Context, uri: Uri): BooleanReturns | Meaning |
|---|---|
true | The URI had scheme kindly and was processed (chat may have been opened, dialogue triggered, etc.). |
false | The URI was not a Kindly URL or start() hasn't been called yet. Forward it to the rest of your app's URL handling. |
Link Interception: KindlySDKInteraction
When a user taps a link inside the chat (in a bot message, button URL, etc.), Kindly normally opens it via an ACTION_VIEW intent. Implement KindlySDKInteraction to intercept links and button taps.
Interface definition
package no.kindly.chatsdk.chat.common
interface KindlySDKInteraction {
/** Called when a chat button is pressed. */
fun didPressButton(chatButton: ButtonChat, chatLog: List<MessageChat>)
/**
* Called before opening a link from the chat.
* Return true to let Kindly open it (default).
* Return false to handle it yourself; Kindly will not open the link.
*/
fun shouldHandleLink(url: String): Boolean = true
}shouldHandleLink has a default implementation returning true, so you only need to override it when intercepting.
Wiring it up
interface is a Kotlin keyword — escape with backticks when assigning:
class ChatLinkHandler : KindlySDKInteraction {
override fun didPressButton(chatButton: ButtonChat, chatLog: List<MessageChat>) {
Log.d("Kindly", "User tapped: ${chatButton.label}")
}
override fun shouldHandleLink(url: String): Boolean {
if (url.startsWith("https://example.com")) {
AppRouter.open(url)
return false
}
return true
}
}
KindlySDK.`interface` = ChatLinkHandler()Hold a strong reference to the handler (e.g., as a property on your Application or singleton); the SDK does not retain it for you.
Combined Pattern: deep links + interception
A common setup: external kindly:// deep links open chat, and in-chat links route through your nav stack instead of a browser intent.
class KindlyIntegration(private val app: Application) : KindlySDKInteraction {
fun boot() {
KindlySDK.start(application = app, botKey = "YOUR_BOT_KEY", market = "YOUR_MARKET")
KindlySDK.`interface` = this
}
fun handleExternal(activity: Activity, uri: Uri): Boolean {
return KindlySDK.handleUrl(activity, uri)
}
override fun didPressButton(chatButton: ButtonChat, chatLog: List<MessageChat>) {
Analytics.track("kindly_button_tap", chatButton.label)
}
override fun shouldHandleLink(url: String): Boolean {
if (url.startsWith("https://example.com")) {
AppRouter.open(url)
return false
}
return true
}
}Receiving Button-Click Details via Events
KindlySDKInteraction.didPressButton gives you the legacy ButtonChat shape. For richer context (button type, exchange info, full chat log) use the kindly:buttonclick event instead — see EVENTS.md:
lifecycleScope.launch {
KindlySDK.events.collect { event ->
val click = event.detail as? KindlyEventDetail.ButtonClick ?: return@collect
Log.d("Kindly", "Tapped [${click.buttonType}] ${click.button.label}")
}
}Both paths fire for the same tap; pick whichever fits your existing wiring.
Notes & Gotchas
- handleUrl(...) returns false if start() hasn't been called — the SDK can't open chat until it's initialized. Either start the SDK in Application.onCreate before forwarding URIs, or queue the URI and replay after start().
- kindly://chat/dialogue/{id} requires the dialogue ID to exist in the bot configuration. Unknown IDs are logged as warnings; chat still opens but no dialogue is triggered.
- Set the intent filter on the activity that handles deep links (often your launcher activity). Without it, Android won't route kindly:// intents to your app.
- Always assign KindlySDK.interface`` with backticks — interface is a reserved word in Kotlin.
- The KindlySDKInteraction is held by the SDK but is replaceable — assigning a new instance overwrites the previous one.
- Use intent.data in both onCreate (cold start) and onNewIntent (warm start with singleTask/singleTop launch modes) to catch all entry points.