Events and error handling
This page covers subscribing to events, the categories of events, the DRException error object, and logging and diagnostics.
For data you keep on screen (the conversation list, unread counts, connection state), subscribe to the matching StateFlow or list snapshot. Events suit one-off actions: showing a toast, navigating, diagnostics or handling temporary messages. Don't use events as your only source of data.
| You want to | See |
|---|---|
| Subscribe to events | Subscribe to events |
| See which events exist | Event categories |
| Check errors, handle cancellation | DRException |
| Turn on logging, export diagnostics | Logging and diagnostics |
Subscribe to events
import com.deeprespond.im.*
im.on<DREvent.MessageSendFailed>().collect { event ->
println("${event.clientMsgId}: ${event.error.code}")
}- You can also use
im.on(DREvent.MessageReceived::class), or collect all events fromim.events. - Collect in a coroutine tied to the lifecycle, and cancel when the screen stops.
- The event stream (
SharedFlow) has a limited buffer; it isn't a message queue that never loses anything. - Events emitted while the app is starting are held for the first subscriber;
startupEventsreads and clears them. Subscribe in one central place, so several screens don't each handle the same launch navigation.
Event categories
| Category | Main events |
|---|---|
| Sign-in and connection | AuthStateChanged, ConnectionStateChanged, SyncCompleted, SyncFailed |
| Storage and configuration | StorageReset, StorageFull, StorageModeChanged, ConfigChanged, SdkUpgradeRequired |
| Users and devices | MeChanged, MeMuteChanged, UsersChanged, DevicesChanged, DevicesNewLogin |
| Foreground/background and time zone | AppStateChanged, ClientStateReported, TimezoneChanged |
| Conversations and messages | ConversationsChanged, ConversationsUnreadChanged, MessagesChanged, MessageReceived, MessageSendFailed, MessageOnline, TypingChanged, ReceiptsChanged |
| Friends and groups | FriendsChanged, BlacklistChanged, FriendRequestsChanged, GroupsChanged, GroupMembersChanged, GroupRequestsChanged |
| Push and presence | PushChanged, PresenceChanged, PresenceSubscribeFailed |
| Calls | CallIncoming, CallChanged, GroupCallChanged |
Native push's opened and registrations, and the system call actions, live on their own controllers, and chat room events live on the room handle. For every event's fields, see Data types.
DRException
import com.deeprespond.im.*
import kotlinx.coroutines.CancellationException
try {
im.messages.send(SendTarget.User("bob"), OutgoingContent.Text("你好"), SendOptions(waitUntilSent = true))
} catch (error: CancellationException) {
throw error // 保持 Kotlin 协程取消语义。
} catch (error: DRException) {
println("${error.code}/${error.reason}; request=${error.requestId}")
}| Field | Meaning |
|---|---|
| code, reason | The error code and details.reason; base your decisions on these |
| details | Extra information as a raw JsonObject |
| source | HTTP, WS or LOCAL |
| status | HTTP status; may be null for connection and local failures |
| requestId | ID for tracing the request |
| retryAfter | A Kotlin Duration; Java uses retryAfterMillis |
Common SDK error codes: network_error, timeout, aborted, not_signed_in, not_connected, local_validation, storage_error, unsupported, permission_required, device_error, invalid_state, send_abandoned. For server errors, see Error codes.
- Cancelling a coroutine throws
CancellationException, which the SDK never wraps as a regular business error. Catch it separately and rethrow it, as above, to keep coroutine cancellation working. - Once a write has been sent or a message is in the send queue, cancelling the wait doesn't undo it on the server; uploads and downloads have their own
cancel. - On
rate_limited, wait forretryAfter; never retry endlessly.
Logging and diagnostics
LogOptions sets level, sink and file. The default level is WARN; you can write logs to a file or hand each LogEntry to your own logging system. Each entry has module, code, reason, requestId and connectionId.
val info = im.connection.diagnostics()
val archive = im.exportDiagnostics()
println(info.connection)
println(archive.name)- Diagnostics include the connection, recent disconnects and failures, sync times, storage mode, clock skew and send queue size.
exportDiagnostics()packs them into a ZIP in the cache directory, and the SDK cleans up old files. Share it through your app's FileProvider, without exposing absolute private paths.- Logs and diagnostics are for troubleshooting; don't write passwords, credentials or message content into them.
For display helpers such as describeError and renderRecall, see UI and lifecycle.
Channel state
Read standalone channel state from StateFlows: im.channels.current, handle.snapshot and handle.tracks, and stop observing when the screen ends.
- Channels have no calling or ringing, so don't use the call events
CallIncomingandCallChangedto infer channel state. - Cancelling the joining coroutine releases the slot it took; even if the
leaverequest fails, the local microphone and camera stop.
See Channels.
Simplified Chinese, Traditional Chinese and English messages
The SDKs ship with message text in Simplified Chinese, Traditional Chinese and English, defaulting to Simplified Chinese, and never call an online translation service:
| Platform | Usage |
|---|---|
| Android | DRLocale.en, DRLocale.zhTW, DRLocale.forLanguage(tag) |
| Web | createLocale() and describeError() from @deeprespond/im-web/locale |
| Flutter | Import en, zhTW and localeForLanguage(tag) from render.dart |
Pass the chosen message table to the display helpers; your app stores the user's language choice. Switching language only changes message text; error codes and user content stay the same.
Server errors may carry message_key and message_params (for building localized messages); keep using code, reason and details to decide what happened. For entry points and how the language is chosen, see Languages and localization.
