Android Chatbot SDK
dialogs_Chatbot_Android embeds a fully functional EnableX Dialogs chatbot into any
Android application. It uses a WebView-Bridge architecture — all chat protocol
logic runs inside an embedded WebView loading a remote chat page, while a
@JavascriptInterface-annotated bridge class (AndroidBridge) connects
JS events to Kotlin/Java code.
First release of the Android Chatbot SDK. See Release Notes for what's included.
Download Android Chatbot SDK v1.0.0Integration Surfaces
Four integration surfaces are provided — pick the one that matches how your app is built:
| Surface | Class | Use When |
|---|---|---|
| XML View | EnxChatClient | Drop into any XML layout, or create programmatically |
| Fragment | EnxChatFragment | Fragment back-stack, multi-pane layouts |
| Headless Session | EnxChatSession | Custom Compose or View UI; no built-in UI needed |
| Compose | EnxChat composable | Pure Jetpack Compose screens |
Requirements
| Tool | Minimum |
|---|---|
| Android Studio | Hedgehog (2023.1.1) or newer |
| Gradle | 8.x |
| Android Gradle Plugin | 8.x |
| minSdkVersion | 24 (Android 7.0) |
| compileSdkVersion | 36 (Android 15) |
| Java | 11 |
| Kotlin | 1.9+ |
Installation
Add to settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
// Add local AAR or private maven if applicable
}
}
Add Dependency
// build.gradle.kts (app module)
dependencies {
implementation("com.dialogs:chatbot-android:1.0.0")
// OR, for the local AAR download from this site:
implementation(files("libs/dialogs_Chatbot_Android-1.0.0.aar"))
}
Required Permissions in AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
CAMERA and RECORD_AUDIO are only required for video/audio click-to-call. Both are runtime permissions on Android 6+; the SDK requests them automatically when a call is initiated.
ProGuard
The SDK ships proguard-rules.pro. If you use R8/ProGuard, ensure the JS bridge class is kept:
-keepclassmembers class com.dialogs_chatbot_android.bridge.AndroidBridge {
public *;
}
Pattern 1 — Quickest (EnxChatClient + bindToLifecycle)
class ChatActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val chatView = EnxChatClient(this)
chatView.bindToLifecycle(this) // auto disconnect/release on lifecycle events
chatView.start(
config = EnxChatConfig(
botId = "YOUR_BOT_ID",
host = "https://dialogs.enablex.io"
)
)
setContentView(chatView)
}
}
Pattern 2 — Fragment Integration
val fragment = EnxChatFragment.newInstance(
botId = "YOUR_BOT_ID",
host = "https://dialogs.enablex.io",
isCallView = true,
enableHistory = true
)
fragment.setSessionListener(mySessionListener)
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null)
.commit()
Pattern 3 — Full Lifecycle Control (with Callbacks)
class ChatActivity : AppCompatActivity() {
private lateinit var chatView: EnxChatClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
chatView = EnxChatClient(this)
chatView.setSessionListener(object : EnxChatSessionListenerAdapter() {
override fun onStateChanged(state: EnxChatState) {
updateConnectionBanner(state)
}
override fun onBotMessage(chatMessage: ChatMessage) {
handleMessage(chatMessage)
}
override fun onVideoCallRequested(iFrameUrl: String) {
// EnxVideoCallActivity is launched automatically by the SDK
}
override fun onError(error: String) {
Log.e("Chat", error)
}
})
chatView.setUiListener(object : EnxChatBotListener() {
override fun onBotConnected(botInfo: BotInfo) {
supportActionBar?.title = botInfo.name
}
override fun onClose() { finish() }
})
chatView.start(EnxChatConfig("YOUR_BOT_ID", "https://dialogs.enablex.io"))
setContentView(chatView)
}
override fun onDestroy() {
chatView.release()
super.onDestroy()
}
}
Pattern 4 — Java Integration
EnxChatConfig config = new EnxChatConfig("YOUR_BOT_ID", "https://dialogs.enablex.io");
EnxChatClient chatView = new EnxChatClient(this);
chatView.bindToLifecycle(this);
chatView.start(config, new EnxChatBotListener() {
@Override public void onBotConnected(BotInfo botInfo) { }
@Override public void onClose() { finish(); }
});
setContentView(chatView);
Pattern 5 — Headless Session (Custom UI)
val session = EnxChatSession.create(this, config)
session.addSessionListener(object : EnxChatSessionListenerAdapter() {
override fun onBotMessage(chatMessage: ChatMessage) {
// Update your own UI
myAdapter.add(chatMessage)
}
})
session.initializeAndConnect()
// Send from your own input:
binding.sendBtn.setOnClickListener {
session.sendMessage(binding.input.text.toString())
}
// Cleanup:
override fun onDestroy() {
session.release()
super.onDestroy()
}
Pattern 6 — Jetpack Compose
@Composable
fun ChatScreen() {
EnxChat(
config = EnxChatConfig(
botId = "YOUR_BOT_ID",
host = "https://dialogs.enablex.io"
),
modifier = Modifier.fillMaxSize()
)
}
Configuration Reference
data class EnxChatConfig(
val botId: String, // Required — from EnableX Dialogs dashboard
val host: String, // Required — e.g. "https://dialogs.enablex.io"
val path: String = "", // Optional URL prefix
val isCallView: Boolean = false, // Show call buttons in header
val enableHistory: Boolean = false // Load previous messages on connect
)
Message Type Handling
override fun onBotMessage(chatMessage: ChatMessage) {
when (chatMessage.messageType) {
MessageType.TEXT -> renderText(chatMessage.text)
MessageType.QUICK_REPLY -> renderChips(chatMessage.quickReplies, chatMessage.allowMultiple)
MessageType.BUTTONS -> renderButtons(chatMessage.buttons)
MessageType.IMAGE -> renderImage(chatMessage.attachments.first().url)
MessageType.VIDEO -> renderVideo(chatMessage.attachments.first().url)
MessageType.AUDIO -> renderAudio(chatMessage.attachments.first().url)
MessageType.FILE -> renderFileCard(chatMessage.attachments.first())
MessageType.CARD -> renderCard(chatMessage)
MessageType.CAROUSEL -> renderCarousel(chatMessage)
MessageType.SYSTEM -> renderSystemNotice(chatMessage.text)
MessageType.UNKNOWN -> Log.w("Chat", chatMessage.rawData.toString())
}
}
Sending Messages Programmatically
// Plain text
session.sendMessage("What are your hours?")
// With attachment
val attachment = ChatAttachment(
uri = fileUri,
type = AttachmentType.DOCUMENT,
fileName = "report.pdf",
mimeType = "application/pdf"
)
session.sendMessage("Please see the attached file", listOf(attachment))
// Reset conversation
session.resetConversation()
Mode A — SDK-Managed (Default)
When the bot initiates a video call, EnxVideoCallActivity is launched automatically. No extra code is needed.
override fun onVideoCallRequested(iFrameUrl: String) {
// Called regardless, even though the SDK also launches the activity — use for analytics
analytics.track("video_call_started")
}
override fun onVideoCallEnded() {
analytics.track("video_call_ended")
}
Mode B — Custom Video UI
Intercept the callback and route the call URL into your own video solution instead:
override fun onVideoCallRequested(iFrameUrl: String) {
MyVideoSDK.join(iFrameUrl)
}
JS Interface Reference
The AndroidBridge is registered on the chat WebView as window.Android.
JS Can Call (Android Exposes)
// Query
Android.isReady() // returns Boolean
Android.getConfig() // returns JSON: {botId, host, path}
// Events JS fires → Android
Android.onChatReady(json)
Android.onConnected(json)
Android.onConversationReady(json)
Android.onMessageReceived(json)
Android.onMessageSent(json)
Android.onTyping(json)
Android.onError(json)
Android.onDisconnected(json)
Android.onBotInfo(json)
Android.onReconnecting(json)
Android.onReconnected(json)
Android.onReconnectFailed(json)
Android.onClear(json)
Android.onConversationReset(json)
Android.onFileSent(json)
Android.onVideoCallRequested(url)
Android.onVideoCallEnded()
Android Injects into JS
initializeEnxChat({botId, host, path}) // or initializeChat(...) depending on chat page build
sendMessageToBot(text, attachments)
sendFileToBot(base64, fileName, mimeType)
resetConversation()
disconnectChat()
Lifecycle Binding Reference
| Lifecycle Event | disconnectOnStop=true | releaseOnDestroy=true |
|---|---|---|
ON_STOP | disconnect() | — |
ON_DESTROY | — | release() |
// Safest — auto-handles everything
chatView.bindToLifecycle(
owner = this,
disconnectOnStop = false, // keep the connection alive when app is backgrounded
releaseOnDestroy = true // always release resources on destroy
)
Feature Support (v1.0.0)
| Feature | Supported | Notes |
|---|---|---|
| Full chat UI (Jetpack Compose) | Yes | Message list, input bar, bot avatar/name/startup message from BotInfo, animated typing indicator. |
| Markdown rendering | Yes | Bold, italic, tables, strikethrough in bot messages. |
| Text messages | Yes | Plain text, user ↔ bot. |
| Quick replies / buttons | Yes | Rendered as chips/buttons; allowMultiple supported on quick replies. |
| Image / video / audio / file attachments | Yes | Both incoming (bot) and outgoing (in-app picker). |
| Cards and carousels | Yes | Structured message rendering. |
| Calendar (date picker) prompts | Yes | |
| System / handover status messages | Yes | |
| Click-to-call (text / audio / video) | Yes | Server-driven via BotInfo.clickToCall — no extra integration code required. Minimal call screen and full-screen EnxVideoCallActivity both provided. |
| Conversation reset | Yes | EnxChatClient.resetConversation() / EnxChatSession.resetConversation(). |
| Conversation history restore | Yes | Optional via enableHistory in EnxChatConfig. |
| Lifecycle-safe auto disconnect/release | Yes | Via bindToLifecycle(). |
| Multi-bot pooling | No | Single bot instance per EnxChatClient / EnxChatSession in this milestone. |
| UI theming API | No | No theming beyond header color and profile picture in v1.0. |
| Offline message queue | No | Messages fail while connection state is DISCONNECTED; monitor onStateChanged and prompt reconnect. |
Known Limitations
| Limitation | Impact | Workaround |
|---|---|---|
| minSdk 24 | Android 7.0+ only | Not supported on older devices |
| WebView-dependent | Requires system WebView ≥ 67 | Prompt users to keep WebView updated |
| Mixed content allowed | HTTP assets can load inside the HTTPS chat page | Serve all chat assets over HTTPS in production |
| Video call is portrait-only | EnxVideoCallActivity locks to portrait | Override orientation in your own activity if landscape is required |
| No offline queue | Messages fail when DISCONNECTED | Monitor onStateChanged and prompt reconnect |
| JS bridge callbacks run off the main thread | AndroidBridge callbacks are not guaranteed on the UI thread | Always wrap UI updates in runOnUiThread {} |
Troubleshooting
Chat Stuck on Loading
- Verify
botIdandhostare correct - Check the
INTERNETpermission is declared in the manifest - Confirm the device has network access
- Check the
onErrorcallback for JS bridge errors
Messages Appear Twice
Do not call sendMessage() from both EnxChatSession and EnxChatClient against the same underlying session.
Video Call Doesn't Open
- Confirm
CAMERAandRECORD_AUDIOpermissions are granted at runtime - Verify
android:hardwareAccelerated="true"is set on your<application>tag
IllegalStateException on WebView.destroy()
Always call release() after disconnect(), never before — releasing tears down the underlying WebView.
Callback Not Reflected in UI
override fun onBotMessage(chatMessage: ChatMessage) {
runOnUiThread {
myAdapter.notifyDataSetChanged()
}
}
ProGuard Stripping the Bridge
-keepclassmembers class com.dialogs_chatbot_android.bridge.AndroidBridge { public *; }
Key Libraries
| Library | Purpose |
|---|---|
| AndroidX Core KTX | Kotlin extensions |
| Jetpack Compose (Material3) | Full chat UI toolkit |
| Lifecycle ViewModel + StateFlow | State management |
| Markwon | Markdown rendering in message bubbles |
| Coil | Image loading and caching |
| Activity Result API | Camera / gallery / file pickers (built-in) |