Hodhod Android SDK tutorial: native in-app support in 10 minutes
Add support chat and tickets to your Kotlin and Compose app with the Hodhod Android SDK: add the dependency, configure it, open the chat, identify users with HMAC, set theme and language, show unread counts, and fix common errors.
In this article
- SDK or WebView widget?
- What the SDK includes
- Requirements
- Step 1: add the dependency
- Step 2: configure it in your Application class
- Step 3: open the chat
- Step 4: identify the user
- Step 5: theme, language and dark mode
- Step 6: the unread counter
- How tickets and the survey behave in the app
- Inbox announcements and warnings
- R8 and ProGuard
- Troubleshooting
- Frequently asked questions
You have an Android app, and your customers ask their questions inside it. The simplest option is to open the web widget in a WebView. But if you care about a native look, the Android keyboard and attachment pickers, and the feel of a real app, the Hodhod Android SDK does the same job without a WebView. It talks to the same inbox and the same widget API as the web widget, so conversations and tickets from the app land in the same shared team inbox.
In this tutorial we connect a Kotlin app to the SDK step by step: installation, configuration, opening the chat, identifying the user, theme and language, and the unread counter. At the end you'll see how tickets and the satisfaction survey behave, and how to fix common errors.
The current SDK version is a beta (
1.0.0-beta04). The class names and parameters below come from that version and may change before the stable release.
SDK or WebView widget?
| Web widget in a WebView | Android SDK | |
|---|---|---|
| Look | A web page inside the app | Native Compose UI with the Hodhod widget design |
| RTL and Persian font | Depends on the page | RTL from the start, with the Vazirmatn font bundled in the SDK |
| Attachments and camera | Limited by the WebView | Photo and file pickers from Android itself |
| Identifying the user | Through JavaScript | One Kotlin call |
| Unread counter on the button | No | Yes |
| Light and dark theme | Depends on the page | Follows the system, or you choose |
| Installation | One snippet | A Gradle dependency |
If your app is just a web page in an Android shell, the web widget is enough. If your app is native and support is part of its experience, the SDK is the better choice.
What the SDK includes
- Inbox contact modes: live chat, ticket, both, or "ticket when offline". The setting you chose for the web widget in the dashboard applies in the app too.
- Tickets: a ticket form with subject, description, category and attachments (as configured on the inbox), a "my tickets" list, and each ticket's thread with a status badge and replies.
- Satisfaction survey (CSAT): three display styles, emoji, stars and numbers 1 to 5, with an optional comment box.
- Inbox announcements and warnings (since
1.0.0-beta04): the same announcements as in the web widget appear at the top of the start screen, with no extra code. - Pre-chat form, working hours and holidays, the issue notice banner, ending a conversation and emailing a transcript, as in the web widget.
- Six languages: Persian, English, Arabic, German, Spanish and French. Other languages fall back to English.
- Light and dark theme, the inbox brand color, and a manual color override.
- Connection loss: when the network or the WebSocket drops, a "no connection" banner appears, the SDK reconnects on its own, and a message that failed to send stays with a "Retry" button.
Requirements
- A "website" inbox in the Hodhod dashboard. If you don't have one yet, follow the live chat installation guide up to creating the inbox.
- The inbox's
websiteToken. The same value is in the widget snippet. minSdk24 or higher, Kotlin and Java 17. The SDK is built withcompileSdk35 and its UI is Jetpack Compose. If your app still uses Views, callHodhod.opento use the ready-made Activity; the Compose dependencies are added automatically.
Step 1: add the dependency
The SDK has two modules: hodhod-core (no UI: API, live connection and logic) and hodhod-ui (the Compose UI and a ready-made Activity). hodhod-ui pulls in hodhod-core, so for the ready-made UI that one is enough.The
SDK is open source (MIT) at github.com/HodHodChat/hodhod-android-sdk and is installed through JitPack, which builds it from the repository's git tag 1.0.0-beta04. First add the JitPack repository to settings.gradle.kts:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
Then add the dependency:
// app/build.gradle.kts
dependencies {
implementation("com.github.HodHodChat.hodhod-android-sdk:hodhod-ui:1.0.0-beta04")
}
The coordinates are com.github.HodHodChat.hodhod-android-sdk:hodhod-ui:1.0.0-beta04 (and ...:hodhod-core:1.0.0-beta04 if you only need the headless core). The Kotlin package names in your code stay chat.hodhod.sdk.
Step 2: configure it in your Application class
Configure the SDK once, in Application.onCreate. Until Hodhod.start(), Hodhod.open() or Hodhod.identify() is called, the SDK sends no network requests.
import android.app.Application
import chat.hodhod.sdk.Hodhod
import chat.hodhod.sdk.HodhodConfig
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Hodhod.configure(
this,
HodhodConfig(
baseUrl = "https://hodhod.chat", // your Hodhod server, no extra path
websiteToken = "YOUR_WEBSITE_TOKEN", // the website inbox token
),
)
Hodhod.start() // optional: so the unread counter works before the chat is opened
}
}
Register the class in AndroidManifest.xml with android:name=".MyApp" on the <application> tag. The base URL must be https://; the only exception is a local server in debug builds, covered under "Troubleshooting".
Step 3: open the chat
There are four ways, starting with the simplest.
1. The ready-made Activity. One line from any Activity or Fragment:
Hodhod.open(context)
This opens HodhodChatActivity: a full-screen page with a theme isolated from your app's theme that handles the back button itself. The same page also opens through the deep link hodhod://chat.
2. Embed it in your own Compose screen. If you want the chat inside your app's navigation:
@Composable
fun SupportScreen(onBack: () -> Unit) {
HodhodChat(modifier = Modifier.fillMaxSize(), onClose = onBack)
}
HodhodChat shows the home screen, chat, tickets and pre-chat form depending on the inbox settings, and calls onClose when the user leaves the first screen or taps the close button. If you need to respect the status bar and navigation bar, handle the insets in modifier; HodhodChatActivity does this with windowInsetsPadding(WindowInsets.systemBars).
3. A floating button with an unread badge. HodhodBubble is a round button that shows the number of unread messages and calls Hodhod.open on click:
Box(Modifier.fillMaxSize()) {
// screen content
HodhodBubble(Modifier.align(Alignment.BottomEnd).padding(16.dp))
}
Pass onClick for different behavior. The button color comes from the inbox, or you can change it with the accent parameter.
4. Your own button. Any button in your app's design, such as "Support" in the profile screen, just calls Hodhod.open(context).
Step 4: identify the user
If the user is signed in to your app, introduce them to Hodhod so conversations and tickets are recorded under their name and can be found on another device too:
Hodhod.identify(
HodhodUser(
identifier = user.id, // a stable user id in your system
identifierHash = hashFromBackend, // HMAC computed on your backend
name = user.name,
email = user.email,
phone = user.phone,
customAttributes = mapOf("plan" to "pro"),
)
) { result ->
result.onFailure { Log.w("Support", "identify failed: ${it.message}") }
}
Without an identifier, the user is an anonymous contact. The result is delivered on the main thread.
Why identifierHash, and why on the backend?
If anyone could send someone else's identifier, they could read that person's conversations. To prevent this, Hodhod keeps a secret HMAC key for each inbox (in the inbox settings, under "User Identity Validation") and checks identifierHash like this: an HMAC with SHA-256 of the identifier using that key, as a hex string.
The key must never be inside the app. After the user signs in, the app gets only the identifierHash value from your own backend. Two examples:
// Node.js
import { createHmac } from 'node:crypto';
const identifierHash = createHmac('sha256', process.env.HODHOD_HMAC_KEY)
.update(String(user.id))
.digest('hex');
# Python
import hashlib, hmac, os
identifier_hash = hmac.new(
os.environ["HODHOD_HMAC_KEY"].encode(),
str(user.id).encode(),
hashlib.sha256,
).hexdigest()
In PHP it's the same: hash_hmac('sha256', (string) $user->id, $key). The identifier string must be exactly the same in the app and on the backend.
If you turn on "Enforce User Identity Validation" in the inbox settings, requests without an identifierHash are rejected. We recommend turning it on so nobody can reach another person's conversations by guessing an identifier.
Custom attributes and logout
Send custom attributes (for example the app version or plan) with HodhodUser or separately. They are queued until the session exists:
Hodhod.setCustomAttributes(mapOf("app_version" to BuildConfig.VERSION_NAME))
When the user signs out of your app:
Hodhod.logout()
This clears the stored session and tokens and closes the connection; the next use creates a fresh anonymous contact. So the next person to sign in on the same phone doesn't see the previous user's conversations.
Step 5: theme, language and dark mode
All of this is set in HodhodConfig:
HodhodConfig(
baseUrl = "https://hodhod.chat",
websiteToken = "YOUR_WEBSITE_TOKEN",
locale = "fa", // fa, en, ar, de, es or fr
darkMode = DarkMode.AUTO, // AUTO (system), LIGHT or DARK
accentColorOverride = 0xFF6A2BC4, // an ARGB color instead of the inbox widget color
)
- Language:
localein the configuration takes priority, then the account language on the server, then the device language. An unsupported language falls back to English. The layout direction (RTL for Persian and Arabic) is set automatically. - Theme:
AUTOfollows the system setting. - Color: if you don't pass
accentColorOverride, the widget color you set for the inbox in the dashboard is used.
Step 6: the unread counter
The unread count is a StateFlow<Int>. It stays at zero until Hodhod.start() or Hodhod.open() has been called, which is why we added start() in step 2. The counter updates live while your app is running.
val unread by Hodhod.unreadCount.collectAsState()
BadgedBox(badge = { if (unread > 0) Badge { Text("$unread") } }) {
Icon(Icons.Outlined.SupportAgent, contentDescription = "Support")
}
If you use HodhodBubble, it shows this counter by itself. The state of loading the settings is in Hodhod.state (Idle, Loading, Ready or Failed).
How tickets and the survey behave in the app
These behaviors are controlled by the inbox settings in the dashboard and need no code:
- Contact mode: in "chat" mode the customer goes straight into a conversation, in "ticket" mode they see the ticket form, and in "both" they pick one. In "ticket when offline", live chat is shown while the team is working and the ticket form opens outside working hours. This mode depends on the working hours defined on the inbox.
- Tickets: after submitting, the ticket number is shown, and from "My tickets" the customer sees the status (open, in progress, waiting for you, resolved, closed) and replies in the same thread. For the difference between tickets and chat, read ticketing vs live chat.
- Survey: after a conversation ends, if the inbox survey is on, a 1 to 5 rating card appears in the style set for the inbox (emoji, stars or numbers). If you care about the result, see what is CSAT.
- After a conversation ends: the customer returns to the start screen and, depending on the contact mode, can open a new chat or ticket.
- Pre-chat form: if it's on for the inbox, it's shown before the first conversation. See the pre-chat form.
Chatbot flows
Chatbot flows run natively in the Android SDK, with no code on your side. If the inbox has an active flow, the app shows it on the start screen: all 17 node kinds, variables and conditions, input validation and ratings. The visitor can be handed off to a live agent or as a ticket, and the same flow analytics as the web widget are recorded. If the flow's require_flow option is on, the direct "start conversation" card stays hidden while the flow is available (it returns if the flow cannot load). The engine was checked against the web engine with 40 parity scenarios (identical steps, variables and events), and the screens were verified on an emulator in Persian (RTL) and English. The flow is edited only in the dashboard.
Inbox announcements and warnings
Since 1.0.0-beta04, the SDK shows the announcements you set up for the inbox in the dashboard at the top of the app's start screen by itself, with no code. The settings are the same as for the web widget (inbox settings, "Widget announcements" section); to learn how to build and word them, read announcements and warnings in the support widget.
- Where they appear: at the top of Home, of the ticket screen when it acts as Home, and of the pre-chat form, above the flow runner and the start cards. The issue notice banner comes after them.
- Up to two, at two levels: a yellow "announcement" with an info icon and a red "warning" with a warning icon, in light and dark themes and right-to-left.
- Text and links: bold parts and links on part of the text. Only
http,https,mailtoandtelopen, in the matching app (browser, dialer, mail) and never in a WebView. - Image: one
httpsimage with a description for TalkBack and an optional link; it is hidden if it fails to load. - Closing: a closable announcement has a close button and the user's choice is remembered on that device. If you change the text, the level or the "Visitors can close it" option in the dashboard, the announcement is shown again.
- Schedule: the server only sends announcements that are switched on and inside their time window; the SDK has nothing to do for it.
- Older servers: if the server doesn't have this feature, no announcement is shown and the rest of the SDK works as usual.
If you built your own UI, the announcements that haven't been closed are in a StateFlow. HodhodChat and Hodhod.open do this themselves and don't need the code below:
val items by Hodhod.repository.announcements.collectAsState() // not closed yet, refreshed with the widget config
// e.g. from a close button of your own UI; ignored for announcements visitors cannot close
items.firstOrNull()?.let { Hodhod.repository.dismissAnnouncement(it.id) }
R8 and ProGuard
The required rules ship inside the libraries (consumer-rules.pro), so you don't need to add anything for a release build. They keep the serializers of the SDK's models and HodhodChatActivity, which Hodhod.open looks up by name. If you already have a rule for the Activity, repeating it does no harm. If you enable shrinkResources, the SDK's strings and fonts are kept by the keep file that ships with it.
Troubleshooting
The chat screen shows an error or doesn't load. Read Hodhod.state; in the Failed state it has a code:
not_found: thewebsiteTokenorbaseUrlis wrong.network: the phone can't reach the server.suspended: the Hodhod account is suspended.server: a server error; try again in a moment.cleartext: you passed anhttp://address.
A local server (10.0.2.2) won't connect. In the emulator, your machine's localhost is 10.0.2.2. Because the address is http://, you need two things, both for debug builds only: pass allowCleartext = true in HodhodConfig, and in a debug network_security_config allow cleartext traffic for 10.0.2.2 only (the SDK's sample app has an example). Don't use either in release builds.
Messages don't arrive live, and the "no connection" banner shows. Live updates run over a WebSocket at the /cable path. A corporate proxy, firewall or CDN may block the WebSocket upgrade. The SDK reconnects with an increasing delay; try the same path on another network.
The language is wrong. Pass locale explicitly in HodhodConfig. Without it, the Hodhod account language (on the server) is used, then the device language. Codes such as fa-IR are accepted and reduced to fa.
identify fails. Usually the identifierHash is wrong. Check that you used the inbox's HMAC key (not the websiteToken), that the identifier is identical in the app and on the backend, and that the hash is written as hex.
Frequently asked questions
How is the Android SDK different from the web widget?
The web widget is a web page that sits on your site with one snippet. The SDK is a native Android UI built with Jetpack Compose that connects to the same inbox and the same API. In both cases conversations land in one shared inbox.
Does my app have to be written in Compose?
No. Hodhod.open(context) works from any Activity. Only embedding HodhodChat and the HodhodBubble button require Compose.
Which Android versions are supported?
minSdk is 24 (Android 7.0 and up).
Do I need a separate inbox?
No. It uses the same "website" inbox, with the same websiteToken. If you want app conversations kept apart, create another website inbox for the app.
Do chatbot flows work in the app?
Yes. If the inbox has an active chatbot flow, the SDK runs it natively on the start screen, with handoff to a live agent or as a ticket. No code is needed beyond the normal setup.
How do you tell contacts apart from app users?
By the identifier you provide. To prevent spoofing, compute identifierHash on your backend with the inbox's HMAC key, and turn on enforced identity validation.
Do the inbox announcements also show in the app?
Yes, since 1.0.0-beta04. The SDK shows the announcements and warnings set up for the inbox at the top of the start screen; no code is needed and closing one is remembered on that device. How to build them is explained in announcements and warnings in the support widget.


