Android quickstart
Install exactly WuKongEasySDK Android 1.0.5 and use contiguous Kotlin code for connection, online messaging, and lifecycle cleanup.
Use one process singleton to install, initialize, listen, send a person message, and clean up with the Activity lifecycle. Alice and Bob run in two independent devices, emulators, or application processes.
The current Product Gateway supports this connection path
The current Product Gateway supports pinned v1.0.5 JSON-RPC CONNECT and online bidirectional messaging, including Android snake_case, camel-case fields, JSON text strings, JSON objects, and Base64 payloads. The tutorial installs Maven Central 1.0.5. The official Android example passed at source revision 7134bbd0263fd01d9e7f71b7bd05b226f75b2292, which is now included in the v1.0.5 Release. The Maven artifact then completed bidirectional messaging and disconnect on a hosted Android 14 / API 34 emulator.
What you will have
- An Android dependency pinned to Maven Central version
1.0.5. - A Kotlin screen that initializes and registers listeners in the required order.
- A two-client acceptance scaffold for Alice's send result and Bob's realtime receive.
- Explicit blockers for singleton identity switching, field compatibility, tokens, and production security.
Run the official example first (recommended)
git clone https://github.com/WuKongIM/WuKongEasySDK-Android.git
cd WuKongEasySDK-Android
git checkout 7134bbd0263fd01d9e7f71b7bd05b226f75b2292
./gradlew test :example:assembleDebug
./gradlew :example:installDebugUse ws://10.0.2.2:5200 from an Android Emulator, not localhost. Run the Official Examples covers server preparation and Alice/Bob acceptance. The source example and Maven 1.0.5 artifact now have separate runtime receipts; keep those evidence classes distinct.
Before you begin
Prepare the following:
- Android 5.0 (API 21) or later.
- An AndroidX application. For an exact reference, this tag itself uses Kotlin
1.9.0, Android Gradle Plugin8.1.4, Gradle8.4, and compileSdk34. - A WuKongIM single-node cluster or multi-node cluster whose
/readyzis healthy and whose WebSocket Gateway is reachable from the Android device. - A product backend that returns
uid, a short-livedtoken, andwebsocketUrlseparately for Alice and Bob.
Read Identity & Token first. Example variables come from the product backend; real tokens never belong in the APK, repository, Logcat, or crash reports.
Step 1: install an exact version
Add this to the application module's build.gradle.kts:
dependencies {
implementation("com.githubim:easysdk-android:1.0.5")
}The equivalent Groovy DSL is:
dependencies {
implementation 'com.githubim:easysdk-android:1.0.5'
}Confirm that gradle.properties enables AndroidX:
android.useAndroidX=trueStep 2: declare network permissions
Add these entries to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />Production uses wss://. Android 9 and later restrict cleartext traffic by default; do not globally enable cleartext as a production TLS workaround.
Step 3: receive connection material from the product backend
data class IMBootstrap(
val uid: String,
val token: String,
val websocketUrl: String,
)The product backend authenticates the current product user and selects a WebSocket address. The Android client receives only its own connection material and no Product HTTP management credential.
Step 4: initialize, listen, and connect
Android EasySDK is a process singleton, and v1.0.5 cannot be initialized a second time with another configuration. This example fits a minimal run with one UID per process. A production application that switches accounts must first design and test a complete logout strategy around this limitation.
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.githubim.easysdk.WuKongConfig
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.enums.WuKongChannelType
import com.githubim.easysdk.enums.WuKongEvent
import com.githubim.easysdk.listener.WuKongEventListener
import com.githubim.easysdk.model.ConnectResult
import com.githubim.easysdk.model.DisconnectInfo
import com.githubim.easysdk.model.Message
import com.githubim.easysdk.model.WuKongError
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
class ChatActivity : AppCompatActivity() {
private val easySDK = WuKongEasySDK.getInstance()
private var listenersRegistered = false
private var connected = false
private val connectListener = object : WuKongEventListener<ConnectResult> {
override fun onEvent(result: ConnectResult) = runOnUiThread {
connected = true
Log.i("EasySDK", "connected")
}
}
private val messageListener = object : WuKongEventListener<Message> {
override fun onEvent(message: Message) = runOnUiThread {
Log.i("EasySDK", "message received")
// Deduplicate by messageId before updating the UI.
}
}
private val disconnectListener = object : WuKongEventListener<DisconnectInfo> {
override fun onEvent(info: DisconnectInfo) = runOnUiThread {
connected = false
Log.i("EasySDK", "disconnected")
}
}
private val errorListener = object : WuKongEventListener<WuKongError> {
override fun onEvent(error: WuKongError) = runOnUiThread {
Log.e("EasySDK", "SDK operation failed")
}
}
fun connect(bootstrap: IMBootstrap) {
val config = WuKongConfig.Builder()
.serverUrl(bootstrap.websocketUrl)
.uid(bootstrap.uid)
.token(bootstrap.token)
.connectionTimeout(15_000)
.requestTimeout(15_000)
.maxReconnectAttempts(5)
.debugLogging(false)
.build()
val current = easySDK.getConfig()
if (current == null) {
easySDK.init(applicationContext, config)
} else {
check(
current.uid == bootstrap.uid &&
current.token == bootstrap.token &&
current.serverUrl == bootstrap.websocketUrl &&
current.connectionTimeoutMs == config.connectionTimeoutMs &&
current.requestTimeoutMs == config.requestTimeoutMs &&
current.maxReconnectAttempts == config.maxReconnectAttempts &&
current.deviceFlag == config.deviceFlag &&
current.debugLogging == config.debugLogging
) {
"WuKongEasySDK v1.0.5 cannot apply changed identity or configuration; " +
"stop and restart the process or upgrade the SDK"
}
}
registerListeners()
connected = easySDK.isConnected()
if (connected) return
lifecycleScope.launch {
runCatching { withTimeout(20_000) { easySDK.connect() } }
.onFailure {
connected = false
easySDK.disconnect()
Log.e("EasySDK", "connect failed")
}
}
}
private fun registerListeners() {
if (listenersRegistered) return
easySDK.addEventListener(WuKongEvent.CONNECT, connectListener)
easySDK.addEventListener(WuKongEvent.DISCONNECT, disconnectListener)
easySDK.addEventListener(WuKongEvent.MESSAGE, messageListener)
easySDK.addEventListener(WuKongEvent.ERROR, errorListener)
listenersRegistered = true
}
override fun onDestroy() {
if (listenersRegistered) {
easySDK.removeEventListener(WuKongEvent.CONNECT, connectListener)
easySDK.removeEventListener(WuKongEvent.DISCONNECT, disconnectListener)
easySDK.removeEventListener(WuKongEvent.MESSAGE, messageListener)
easySDK.removeEventListener(WuKongEvent.ERROR, errorListener)
listenersRegistered = false
}
if (isFinishing) easySDK.disconnect()
super.onDestroy()
}
}Listeners are registered only after init, and the same object references are retained for removeEventListener. The outer withTimeout bounds the complete wait at 20 seconds and disconnects after failure; SDK connection and request limits are 15 seconds, with at most 5 reconnect attempts. On Activity recreation, the page reads isConnected() instead of waiting for a CONNECT event that will not be replayed. A changed identity, token, URL, or connection setting stops explicitly rather than silently retaining a stale value. The example sets debugLogging(false) explicitly. Maven 1.0.5 applies that master gate to protocol-parsing and event-dispatch diagnostics and redacts public model strings. A production application normally lets an Application-scoped connection manager own the SDK while Activities or Fragments subscribe only to UI events. The screen-level owner above makes the minimal lifecycle visible.
Step 5: send the first message
fun sendText(targetUid: String, text: String) {
check(connected) { "EasySDK is not connected" }
lifecycleScope.launch {
val payload: String = org.json.JSONObject()
.put("type", 1)
.put("version", 1)
.put("content", text)
.toString()
runCatching {
easySDK.send(
channelId = targetUid,
channelType = WuKongChannelType.PERSON,
payload = payload,
)
}.onSuccess {
Log.i("EasySDK", "SEND completed")
}.onFailure {
Log.e("EasySDK", "send failed")
}
}
}A returned send result means Alice obtained a result for the request. Bob must independently observe the message through his MESSAGE listener. Do not mark the peer as “received” inside Alice's send callback.
Step 6: accept with Alice and Bob
Because the SDK is a process singleton, use two devices, two emulators, or two independent application processes:
- Alice and Bob each obtain their own connection material from the product backend.
- Both sides observe
CONNECT; on failure, record only a stable error code and stage, never the original error text. - Alice calls
sendText("bob", ...)and records the send result. - Bob checks
fromUid,channelId,messageId, and the payload. - Bob sends back to Alice and proves the reverse direction.
- Recreate the Activity and confirm listeners are not duplicated; exit the application and confirm the connection closes.
That exact source ran against the same WuKongIM revision on an Android 14 / API 34 emulator and completed bidirectional messaging, manual disconnect, and heartbeat-timeout verification. The Maven 1.0.5 artifact then passed instrumentation bidirectional messaging and disconnect on a hosted API 34 emulator. Retain the server revision, SDK revision, package-resolution result, device, and network environment, and do not hide failures with blind retries. Physical-device execution, log redaction, offline recovery, and production security require separate acceptance.
Troubleshooting
SDK is not initialized: preserve theinit → addEventListener → connectorder.- The same UID gets a refreshed token or route and configuration changes:
v1.0.5has no public reset API. Stop explicitly and restart the process; do not continue with a stale token or URL. - The emulator connects but a physical device does not: confirm that the address is reachable outside the host or container and have the product backend return the device-reachable WSS ingress.
- CONNECT, SEND, or RECVACK parsing fails: confirm the server includes the EasySDK JSON-RPC compatibility implementation. The current server accepts snake_case fields and the README-aligned Android JSON text-string payload, as well as JSON objects and Base64 payloads, and returns snake_case results for Android. Use APP
0, WEB1, and PC2; do not carry forward older literals. - Raw JSON or
Paramsstill appears in Logcat withdebugLoggingdisabled: first confirm Gradle resolved1.0.5, clean the old build, then use a non-production canary to distinguish SDK output from application or third-party network logs. Stop the rollout and file a minimal reproduction if the Release artifact still exposes that canary. - Messages are duplicated: prevent repeated listener registration and deduplicate by
messageId.
Before production
- Use
wss://and verify certificates, proxy Upgrade, connection timeouts, and network recovery from a physical device. - Handle the
v1.0.5restriction on reinitializing with changed configuration; account switching must not retain a stale UID, token, or route. - Release diagnostics are default-silent. Keep
debugLogging(false)in production and use non-production canaries to confirm that Logcat and crash reports omit tokens, payloads, raw JSON, and error details. - Check device wire values as APP
0, WEB1, and PC2, and retain the Release build, Android version, device, network, and server revision. - Continue with Integration Acceptance for offline behavior, push, multi-device, capacity, upgrades, and rollback.
Next
Return to the WuKongEasySDK overview, then read Messaging and the Release Checks.