implementation("io.signalbird:signalbird-sdk:2.10.1")signalbird-sdk - Kotlin (Android)
It carries two clients:
| Class | What for | Key | Where |
|---|---|---|---|
SignalbirdApp |
live chat + push registration for the end user | sb_public_live_… (public) |
the Android app |
SignalbirdClient |
Radio - writing logs and events | sb_secret_live_… (secret) |
server-side Kotlin (Ktor, Spring) |
A secret key is never embedded in the APK: the package can be unpacked and the key ends up in everyone's hands. The mobile side does chat and push registration with the public key.
Install
// build.gradle.kts
dependencies {
implementation("io.signalbird:signalbird-sdk:1.9.0")
}<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />The only external dependency is coroutines, which every Android project already
has. For JSON it uses Android's built-in org.json: OkHttp, Retrofit or Moshi
are not forced on you, because an SDK choosing the customer's networking and
serialization stack is the most common source of version conflicts.
Storing the visitor secret
Supplying a storage is mandatory. The default is an in-memory store meant only for tests: if the secret does not survive on the device, the user loses their chat history every time they open the app.
class PrefsStorage(context: Context) : SignalbirdStorage {
private val prefs = context.getSharedPreferences("signalbird", Context.MODE_PRIVATE)
override fun get(key: String): String? = prefs.getString(key, null)
override fun set(key: String, value: String) { prefs.edit().putString(key, value).apply() }
override fun remove(key: String) { prefs.edit().remove(key).apply() }
}The stored record carries the app key too: if you rotate the key, the old identity is discarded automatically and a new session opens.
Live chat
import io.signalbird.sdk.*
val client = SignalbirdApp(SignalbirdAppConfig(
publicKey = "sb_public_live_…",
chatKey = "destek",
pushKey = "mobil",
locale = "en",
storage = PrefsStorage(applicationContext),
))chatKey is the chat channel's module key and pushKey the push channel's (the channel names you create in the panel; not secret). The SDK sends pushKey on device and push endpoints (registerDevice, unregisterDevice, reportPushOpened) and chatKey on chat endpoints, as the X-Signalbird-Module-Key header; if the relevant key is missing the API returns 400 MODULE_KEY_MISSING (2.10.0).
You get the app key from the panel (Inbox → Settings → Apps) or by calling
createApp with the Management SDK.
Bootstrap
lifecycleScope.launch {
val boot = client.bootstrap()
val app = boot.json?.optJSONObject("app")
if (app?.optBoolean("chat_enabled") != true) return@launch // chat is off
}Session and conversation
lifecycleScope.launch {
client.startSession(mapOf("name" to "Ayşe", "email" to "ayse@example.com"))
val started = client.startConversation("Where is my parcel?")
val conversationId = started.json
?.optJSONObject("conversation")
?.optString("id") ?: return@launch
client.sendMessage(conversationId, "Order number: 1234")
}Fetching new messages
val updates = client.getConversation(conversationId, after = lastMessageId)There is no WebSocket. Poll every 3 seconds while the screen is in front and on a gradually widening interval in the background, skipping the round while the app is backgrounded: this recovers by itself when the connection drops and does not burn battery.
// Polling bound to the lifecycle
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
while (isActive) {
client.getConversation(conversationId, after = lastMessageId)
delay(3_000)
}
}
}Interactions
client.setTyping(conversationId, true)
client.markRead(conversationId, lastMessageId)
client.reactToMessage(conversationId, messageId, "👍")
// Only their own message, and only within 15 minutes of sending it
client.editMessage(conversationId, messageId, "Correction")
client.deleteMessage(conversationId, messageId)
client.rateConversation(conversationId, rating = 5, comment = "Thank you")
client.closeConversation(conversationId)Identity
client.identify(mapOf(
"external_id" to user.id,
"email" to user.email,
"name" to user.displayName,
"attributes" to mapOf("plan" to "pro"),
))
client.signOut() // clears the local identity; the record on the server staysPush (FCM)
class SignalbirdMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
CoroutineScope(Dispatchers.IO).launch {
client.registerDevice(
token = token,
externalId = currentUser?.id,
deviceName = Build.MODEL,
appVersion = BuildConfig.VERSION_NAME,
)
}
}
}Asking for the Android 13+ notification permission (POST_NOTIFICATIONS) and
obtaining the token is the app's job: when you ask is a product decision.
The SDK only forwards the token.
On sign-out:
client.unregisterDevice(token) // the record is deactivated, not deletedRadio (server-side Kotlin)
val radio = SignalbirdClient(SignalbirdConfig(
domainKey = System.getenv("SIGNALBIRD_DOMAIN_KEY"),
source = "api-01",
))
radio.critical("critical", "the payment service is not responding", mapOf(
"service" to "iyzico",
))
radio.batch(listOf(
mapOf("channel" to "deploy", "message" to "v2.4.0 is live", "level" to "info"),
))Five levels: SignalbirdLevel.DEBUG … CRITICAL.
Result shape
data class SbResult(
val ok: Boolean,
val status: Int,
val data: Any?, // JSONObject, JSONArray or null
val code: String?,
val message: String?,
) {
val json: JSONObject?
val array: JSONArray?
}val result = client.listConversations()
result.json?.optJSONArray("data")?.let { list ->
for (i in 0 until list.length()) {
val conversation = list.getJSONObject(i)
// …
}
}Version notice
The SDK reports its version on every request: X-Signalbird-Sdk: kotlin/<version>.
When a newer version is released the API responds with
Signalbird-Sdk-Status: outdated (or unsupported once it is no longer
supported) and the SDK writes a warning once per process (System.err, logcat on Android).
The warning is not an error: the request result does not change.
In the panel, Domains → [domain] → Keys shows which SDK version each
key is used with; keys on an old version also get a banner at the top of the
panel and a notification. To update, bump io.signalbird:signalbird-sdk in build.gradle.kts.
Error codes
| Code | Meaning |
|---|---|
WRONG_KEY_TYPE |
The constructor throws IllegalArgumentException |
VISITOR_INVALID |
The secret is invalid; a new session must be opened |
CHAT_UNAVAILABLE |
The quota is full or chat is off |
MODULE_DISABLED |
Your package does not include the chat or push module |
NETWORK_ERROR / TIMEOUT |
Network (status 0) |
Notes
- Every method is
suspendand runs onDispatchers.IO; you do not have to manage a thread on top of that. - PATCH requests go out as POST plus an
X-HTTP-Method-Overrideheader: Android'sHttpURLConnectiondoes not know the PATCH method. On the server side there is no difference. - Apart from the key check in the constructor, no method throws; the result is always the envelope.