Events and error handling
The Flutter SDK tells you about data and state changes through events, and reports every error as a DRException. This page lists all events and their fields, the structure of the error object, all error codes with handling advice, and what the SDK does automatically when offline or when something fails.
Most UIs don't need to handle data-change events directly: LiveLists such as the conversation and friend lists, and message lists, update themselves; see Using it in Flutter UIs. Events are mainly for three things: sign-in and connection changes (returning to the sign-in page, showing "Connecting…"), alerts (new message notifications, incoming calls), and knowing when to re-read values you read synchronously (unread total, typing and so on).
| You want to | See |
|---|---|
| Subscribe and unsubscribe | Subscribe and unsubscribe |
| Look up an event's fields | All events |
| Keep badges, status and other synchronous values current | Reading synchronous values with events |
| Check errors and look up error codes | The error object, SDK error codes, Common server error codes |
| Show error messages to users | Showing error messages |
| Know what the SDK retries automatically | Offline and retries |
Subscribe and unsubscribe
Events are Dart Streams. im.on<T>() subscribes to one type of event, and im.events is all events:
import 'dart:async';
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
final StreamSubscription<MessageReceived> sub = im.on<MessageReceived>().listen((e) {
if (e.notify.notify) showToast('${e.message.sender}:新消息');
});
// 不再需要时取消
await sub.cancel();
// 只等一次
final first = await im.on<SyncCompleted>().first;
debugPrint('第一次同步完成:${first.trigger}');- Each event is a class extending
DREvent.DREventissealed, so when subscribing toim.eventsyou can handle events by type withswitch(see below). - Both are broadcast streams that can be subscribed to in several places at once. Events emitted before you subscribe aren't replayed; when you need the current value, read it first (see Reading synchronous values with events).
- Events are emitted in the main isolate. Exceptions thrown by a listener go to the current Zone's uncaught error handler (
PlatformDispatcher.instance.onErrorin Flutter) and don't affect other listeners or the SDK. - Data-change events fire after local data is updated. Payloads only say what changed (such as which conversations), and you read the latest data from the matching list or method.
- A change is emitted only once: the result of the user's own action and the server notification that follows are merged, so there are no duplicate events.
- The client lives as long as the process and has no destroy method; subscriptions made in widgets are cancelled in
dispose(); see Using it in Flutter UIs.
Sign-in and connection state also have im.auth.stateStream and im.connection.stateStream, which give the new state directly, the same as AuthStateChanged and ConnectionStateChanged.
Handling several events in one place:
final sub = im.events.listen((event) {
switch (event) {
case AuthStateChanged(state: SessionEnded()):
goToLoginPage();
case ConnectionStateChanged(:final state):
debugPrint('连接状态:${state.kind}');
case StorageFull():
showToast('手机存储空间不足,请清理后重试');
case SdkUpgradeRequired(:final minVersion):
showToast('请升级到 $minVersion 或以上的版本');
default:
}
});All events
All of the event classes below are exported from package:deeprespond_im_flutter/deeprespond_im_flutter.dart.
Sign-in and connection
| Event | Fields | Description |
|---|---|---|
AuthStateChanged | state: AuthState | Sign-in state changed: SignedIn, SignedOut, SessionEnded (with code and reason), Suspended (service suspended); see Initialization, sign-in and connection |
ConnectionStateChanged | state: DRConnectionState | Connection state changed: ConnectionIdle, ConnectionConnecting, ConnectionAuthenticating, ConnectionSyncing, ConnectionReady, ConnectionWaiting, ConnectionOffline, ConnectionStopped, with kind as the matching string; see Initialization, sign-in and connection |
SdkUpgradeRequired | minVersion: String | The SDK is older than the minimum version the server requires. The connection has stopped, local data is read-only, and writes reject with invalid_state (upgrade_required). Ask the user to update the app |
AppLifecycleReported | background: bool; badge: int? | When the app went to the background, the SDK reported the state to the server; badge is the badge count reported (iOS and Android only) |
PushOpened | payload: PushPayload | The user tapped an IM notification; for a cold start from a notification tap, it's emitted right after the client is created. Requires deeprespond_im_push; see Offline push |
Sync and local data
| Event | Fields | Description |
|---|---|---|
SyncCompleted | modules: List<SyncModule>; trigger: SyncTrigger | A sync round finished. modules lists the modules synced (config, me, friends, groups, messages, push, calls, chatrooms, presence). trigger is what started it: login (after sign-in), reconnect (after reconnecting), syncRequired (the server requested a full sync), hint (the server signaled new data), foreground (returned to the foreground), periodic (a periodic check), fallback (periodic sync while the connection is unavailable) |
SyncFailed | module: SyncModule; error: DRException | A module failed to sync. The SDK retries automatically; usually just log it |
StorageReset | reason: String; droppedMessages: int? | Local data was reset. reason: reset_required (the server required a reset), schema_downgrade (after the app was downgraded, the local database was deleted and rebuilt), corrupted (the local database couldn't be opened and was rebuilt), reloaded (local data was reloaded). Open lists have re-read automatically; you only need to re-read data you stored yourself. droppedMessages is the number of unsent messages dropped as a result |
StorageFull | none | The disk was full when writing local data (emitted at most once every 10 minutes). The SDK has already deleted other users' local data on this device; ask the user to free up space, and you can also clear the image cache (DRImageCache.instance.clear()) |
Runtime configuration and the user
| Event | Fields | Description |
|---|---|---|
ConfigChanged | config: ClientConfig? | The app's runtime configuration changed; same as im.config. null after sign-out |
MeChanged | me: SelfProfile? | The user's profile changed; same as im.users.me(). null after sign-out |
MeMuteChanged | scope: String; muted: bool; expiresAt: DateTime? | The user was globally muted or unmuted (received during the current connection); scope is chat, group or room |
UsersChanged | usernames: List<String> | Other users' cached profiles updated; UIs that use im.users.get() should re-read |
DevicesChanged | devices: List<OnlineDevice> | The user's online devices changed; same as im.devices.online() |
DevicesNewLogin | sessionId, deviceName, platform, loginMethod: String; createdAt: DateTime? | The user signed in on a new device; you can show "Your account signed in on XX" |
Conversations and messages
| Event | Fields | Description |
|---|---|---|
ConversationsChanged | upserted, removed: List<String> | Conversations in the list were added, changed or removed; values are conversationKeys |
ConversationsUnreadChanged | total: int; source: String | The unread total changed; total matches im.conversations.getUnreadTotal(), and source is local or server |
MessagesChanged | conversationKey: String; upserted, removed: List<int>; pending: List<String>; renamedFrom: String? | Messages in a conversation changed. upserted and removed are message sequence numbers; pending lists changed clientMsgIds in the send queue. renamedFrom means a draft conversation became a real one, and holds the old draft key |
MessageReceived | message: MessageView; notify: NotifyDecision | A new message from someone else, for alerts: notify says whether it should alert (mutes and quiet hours already considered). Only emitted for messages received in real time, not for backfilled history or messages from while offline synced after reconnecting |
MessageSendFailed | clientMsgId, conversationKey: String; error: DRException | A message finally failed to send (automatic retries ran out, or the server rejected it). The message stays in the list with status failed, so the user can resend or delete it |
MessageOnline | message: OnlineMessage | An online-only message (sent with sendOnline, such as custom signaling like "the other user is doing something"). Not stored |
TypingChanged | conversationKey: String; usernames: List<String> | Who is typing in a conversation changed |
ReceiptsChanged | conversationId: String; seqs: List<int> | Read counts of group messages changed; read them with im.messages.getReceipt() |
Contacts and groups
| Event | Fields | Description |
|---|---|---|
FriendsChanged | upserted, removed: List<String> | Friends added, profile or remark changed, or removed; values are usernames |
BlacklistChanged | upserted, removed: List<String> | The blocklist changed |
FriendRequestsChanged | lists: List<String>; unreadCount: int | Received (received) or sent (sent) friend requests changed; unreadCount is the number of unread received requests |
GroupsChanged | upserted, removed: List<String> | "My groups" or group information changed; values are group IDs |
GroupMembersChanged | groupId: String; usernames: List<String>? | Group members changed; usernames: null means the whole member list changed |
GroupRequestsChanged | list: String; groupId: String? | Group requests changed: pending is a group's requests awaiting approval (with groupId), invitations the user's received invitations, applications the user's join applications |
Push settings and presence
| Event | Fields | Description |
|---|---|---|
PushChanged | settings: bool; mutes: List<MuteTarget> | Push settings (settings: true) or conversation mutes changed; mutes lists conversations whose mute changed |
PresenceChanged | items: List<PresenceState>; reset: bool; cleared: List<String> | Presence of subscribed people changed. reset: true means all local presence was cleared (for example after reconnecting); cleared lists people whose status was cleared |
PresenceSubscribeFailed | usernames: List<String>; reason: PresenceFailure? | People whose subscription didn't take effect: denied (not allowed to view), limitExceeded (over the subscription limit), presenceDisabled (presence isn't on for the app) |
Audio and video
| Event | Fields | Description |
|---|---|---|
CallIncoming | call: CallHandle | An incoming call started ringing. In the foreground you show the incoming-call screen; in the background deeprespond_im_call shows the system incoming-call UI |
CallChanged | call: CallHandle; change: CallChange? | A call's status, members or other details changed. change: null means it was caused by this device's own action or a resync |
GroupCallChanged | groupId: String; banner: GroupCallBanner? | An ongoing call in a group changed, for showing a "group call in progress" banner. When the call ends, the banner's status is ended; when a query finds no call in the group, banner is null |
For details on calls, see Audio and video calls. Chat room messages, member entry and exit and so on are subscribed to on the object you get when entering a room; see Chat rooms.
Reading synchronous values with events
These values can be read synchronously, and an event fires when each changes. Subscribe to the event and re-read to keep your UI current:
| Read | Event on change |
|---|---|
im.auth.state, im.auth.currentUser | AuthStateChanged |
im.connection.state | ConnectionStateChanged |
im.config | ConfigChanged |
im.users.me() | MeChanged |
im.users.myMute | MeMuteChanged |
im.users.get(username) | UsersChanged |
im.conversations.getUnreadTotal() | ConversationsUnreadChanged |
im.conversations.getTyping(conversationKey) | TypingChanged |
im.friends.get(username), im.friends.listInfo | FriendsChanged |
im.friends.blacklist.has(username) | BlacklistChanged |
im.friends.requests.unreadCount | FriendRequestsChanged |
im.messages.getReceipt(conversationId, seq) | ReceiptsChanged |
im.push.settings(), im.push.getMute(target) | PushChanged |
im.presence.get(username) | PresenceChanged |
im.devices.online() | DevicesChanged |
im.calls.current, im.calls.incoming | CallIncoming, CallChanged |
im.calls.groupCallBanner(groupId) | GroupCallChanged |
When a value hasn't changed, reading returns the same object. These values may also change on sign-in, sign-out or user switch, and the matching events fire then too.
import 'package:flutter/material.dart';
/// 显示未读总数的角标。
class UnreadBadge extends StatelessWidget {
const UnreadBadge({super.key, required this.im});
final DRClient im;
@override
Widget build(BuildContext context) => StreamBuilder<ConversationsUnreadChanged>(
stream: im.on<ConversationsUnreadChanged>(),
builder: (context, _) {
final total = im.conversations.getUnreadTotal(); // 每次重建时读取
return total > 0 ? Badge(label: Text('$total')) : const SizedBox.shrink();
},
);
}The error object
Every SDK error is a DRException: methods returning a Future fail with it, synchronous methods throw it, and events such as MessageSendFailed and SyncFailed carry it.
try {
await im.groups.leave('g_123');
} on DRException catch (e) {
debugPrint('${e.code} ${e.reason} ${e.details} ${e.status} ${e.requestId} ${e.retryAfter} ${e.source}');
}| Field | Type | Description |
|---|---|---|
code | String | Error code: the server's error code, or one of the SDK's own (see below) |
reason | String? | Reason, i.e. details['reason'] |
details | Map<String, Object?>? | The server's details as is, read-only; for SDK errors, extra information |
status | int? | HTTP status; absent for connection errors and SDK errors |
requestId | String? | The server's request_id; give it to support |
retryAfter | int? | Suggested wait in seconds |
source | ErrorSource | Where the error came from: ErrorSource.http (an HTTP request), ErrorSource.ws (the connection), ErrorSource.local (the SDK locally) |
message | String | A description for debugging. Don't show it to users; for user-facing text, see Showing error messages |
cause | Object? | The original exception behind the error (such as a SocketException), for troubleshooting only |
Decide based on code and reason, not status or message. The SDK's own error codes have constants in LocalErrorCode (such as LocalErrorCode.networkError).
How it relates to server error codes
Server errors are passed to you as is: code and details match the server docs, and source is http or ws. For each code's meaning and its details values, see the server error codes. Client-specific codes (such as user_muted, group_muted, not_friend, user_blocked) are under error codes for console and client APIs.
Before sending a request, the SDK checks locally and rejects anything the server would certainly reject, with source local:
- Features that are off or not permitted (such as group read receipts not enabled, or a regular member mentioning everyone) use the server's
permission_deniedwith the same reason, such asread_ack_disabled,mention_all_deniedorpresence_disabled. - Going over the server's fixed rate limits (such as "mark all read" twice per minute) uses
rate_limitedwithretryAfter. - Invalid parameters or content (size, format, required fields, unsupported characters and so on) always use
local_validation, withdetails['reason']anddetails['field']explaining why.local_validationalways means the request wasn't sent.
That way you handle "unavailable or not permitted" and "too frequent" once, by the server's error codes.
SDK error codes
code | Constant | Meaning | What to do |
|---|---|---|---|
network_error | networkError | The network is unavailable or the request didn't arrive | Ask the user to check the network and retry later |
timeout | timeout | The request timed out | Retry later; for writes, check the result first; see Offline and retries |
aborted | aborted | Cancelled by your DRCancelToken or cancel() | No message |
not_signed_in | notSignedIn | A method that needs sign-in was called while signed out | Sign in first |
not_connected | notConnected | An operation that must go over the connection (such as some chat room operations) was called while disconnected | Try again once connected |
local_validation | localValidation | Failed a local check; the request wasn't sent. reason is the reason (such as required, too_large, control_characters, url_not_allowed), and details['field'] the field | Ask the user to correct it |
storage_error | storageError | Reading or writing local data failed; reason quota_exceeded means the disk is full (StorageFull is emitted too) | Ask the user to free up space, or reopen the app |
unsupported | unsupported | Not supported by this platform or deployment: reason is media_disabled (no file service), platform (such as voice recording on desktops), sqlcipher_required (encryptDatabase is on but the app isn't built with SQLCipher), chatroom_attachment (chat rooms can't send files) and so on | Hide the feature, or fix the configuration |
permission_required | permissionRequired | The user hasn't allowed the microphone or camera; details['permissions'] lists what's missing (microphone, camera) | Guide the user to allow it in system settings |
device_error | deviceError | The microphone or camera is missing, in use or failing. details['device'] is microphone or camera (during calls), and reason says why | Ask the user to check the device |
invalid_state | invalidState | Can't be called in the current state. reason says why: disposed (the list was released), signed_in (can't clear the current user's local data), upgrade_required (the SDK is too old and local data is read-only), recording and not_recording (wrong recording state) and so on | Usually a call-order problem; check your code |
send_abandoned | sendAbandoned | The SDK gave up sending a message. reason is attachment_lost (the attachment is gone: attachments over OutboxOptions.persistAttachmentMaxBytes, 200 MB by default, aren't copied, and the original was deleted or moved before sending) or expired (not sent within OutboxOptions.maxAge, 48 hours by default) | Ask the user to send it again |
Reasons for local_validation
details['reason'] (i.e. reason) of local_validation says what's wrong, and details['field'] names the field (such as body.text or username):
reason | Meaning |
|---|---|
required | A required parameter or field is empty |
invalid_format | Wrong format, such as appKey, apiUrl or client_msg_id |
invalid_value | The value isn't one of those allowed, or the file doesn't exist |
invalid_type | The parameter has the wrong type |
out_of_range | A number is out of range |
too_long | Text is too long |
too_large | Content or a file is too large, such as a message over max_message_body_bytes in the runtime configuration or an attachment over max_upload_bytes |
too_many_items | Too many items at once, such as deleting more than 100 messages in one call |
control_characters | The text has unsupported control characters (only text message bodies allow line feeds, carriage returns and tabs) |
invalid_body, unknown_type | The message content doesn't fit the message type; the message type isn't supported |
mentions_not_allowed, receipt_not_allowed | Mentions and group read receipts only work in group chats |
url_not_allowed | The app only accepts this service's file URLs, and this URL doesn't qualify |
invalid_purpose | This file can't be used here (such as a video as an avatar) |
image_too_large, unsupported_type | The file can't be uploaded as the requested type; see Files and images |
invalid_ext, invalid_priority, invalid_attribute_value, attributes_too_large | A chat room extension, message priority or attribute doesn't qualify |
too_many_invitees | Too many people invited to a call at once |
invalid_evidence, self_report | Report evidence doesn't qualify; you can't report yourself |
not_found | The conversation, message or file to act on isn't found locally (such as resending a message no longer in the send queue) |
describeError has specific messages for the common reasons; the rest show a generic "content doesn't meet the requirements" message.
Common server error codes
code | Meaning | What to do |
|---|---|---|
invalid_argument | Parameters break the server's rules | Ask the user to correct it; reason says why |
unauthenticated, session_revoked | Sign-in is no longer valid | Handled by the SDK (refresh or end the session); you only handle SessionEnded in AuthStateChanged |
invalid_credentials | Wrong username, password or ticket | See Initialization, sign-in and connection |
permission_denied | Not permitted, or the feature is off; reason says why | Show a message based on the reason, or hide the feature |
not_found | The user, group, conversation, message or similar doesn't exist or was deleted | Show "This doesn't exist or was deleted" |
already_exists | What you're creating already exists | Treat as success, or tell the user |
version_conflict | The data was changed | Re-read, then submit again |
limit_exceeded | A count limit was exceeded; reason says which | Tell the user the limit is reached |
payload_too_large | Content or a file is too large | Ask the user to shorten the content or use a smaller file |
rate_limited | Too frequent | Retry after retryAfter; the SDK already waits and retries requests that are safe to retry |
too_many_attempts | Too many wrong passwords | Ask the user to try again after retryAfter |
message_rejected, content_rejected | The message or profile failed content moderation | Ask the user to change the content. When reason is app_rejected, message is the text your server gave and can be shown directly |
user_muted, group_muted | The user is globally muted, muted in the group, or the group has mute-everyone on | Show that they're muted; details['expires_at'] is when it ends |
user_blocked | Blocked by the other user | Show "The other user isn't accepting your messages" |
not_friend | The app only allows messaging friends, and they aren't friends | Ask the user to add them as a friend first |
not_group_member | The user is no longer in the group | Tell the user and refresh the UI |
tenant_unavailable, app_unavailable | The service is suspended or read-only | Show "Service temporarily unavailable"; the SDK enters the Suspended state and continues automatically when service resumes |
internal | Server failure | Retry later; if it persists, contact support with the requestId |
Showing error messages
describeError in package:deeprespond_im_flutter/render.dart gives Chinese message text based on code and reason, such as "发送太频繁,请稍后再试" (sending too fast, try again later), "群主已开启全员禁言" (the owner muted everyone) or "你已被禁言,至 10 月 5 日 18:00" (you're muted until 18:00 on October 5):
import 'package:deeprespond_im_flutter/render.dart';
im.on<MessageSendFailed>().listen((e) => showToast(describeError(e.error)));
try {
await im.friends.requests.send(username: 'lisi', message: '你好');
} on DRException catch (e) {
showToast(describeError(e));
}Lookup order is code.reason first (such as permission_denied.mention_all_denied), then code; if neither exists, a generic "server busy, try again later" message is shown. Exception: for message_rejected and permission_denied with reason app_rejected, the server's message (the text your server gave) is shown. Mutes (user_muted) with an end time show it in local time.
To change some messages or use another language, pass locale. The built-in Chinese strings are zhCN; replace entries with copyWith. The keys of the errors table are the code or code.reason described above, and keys you leave out keep their originals:
import 'package:deeprespond_im_flutter/render.dart';
final myLocale = zhCN.copyWith(errors: {
'rate_limited': '手速太快啦,休息一下',
'permission_denied.mention_all_denied': '只有管理员可以 @所有人',
});
showToast(describeError(err, locale: myLocale));
debugPrint('${zhCN.errors.keys}'); // 内置的全部条目You can show why a session ended (SessionEnded) the same way: describeError(DRException(code: ended.code, details: {'reason': ended.reason}, source: ErrorSource.http)).
Offline and retries
The SDK handles errors automatically by type, and you only handle the error that finally comes back:
| Category | Error codes | What the SDK does |
|---|---|---|
| Sign-in invalid | unauthenticated, session_revoked | For expired tokens, refreshes and resends the request once; for revoked sessions, ends the session (SessionEnded) |
| Suspended or read-only | tenant_unavailable, app_unavailable | Returns it to you; once confirmed as a suspension, enters the Suspended state and continues automatically when service resumes |
| Rate limits | rate_limited, too_many_attempts | Waits and retries automatically when the wait is 60 seconds or less; longer waits are returned to you. Never retried automatically: friend requests, group creation, group invitations, join applications, starting and inviting to calls, reports, chat room messages, and too_many_attempts |
| Temporary failures | network_error, timeout, internal, HTTP 5xx | Requests safe to resend (queries, writes with an idempotency key, naturally idempotent operations such as marking read) are retried with backoff, up to 3 times. Other writes aren't retried and are returned to you |
| Needs the user | All other codes | Returned to you; show a message based on code and details |
Sending messages has its own send queue:
im.messages.send()returns once the message is written to the send queue; the SDK sends it from there. While offline, messages wait in the queue and are sent when the network returns. If the app is killed, sending continues the next time it starts and signs in.- On network errors, timeouts or server failures, sending retries at intervals of 1, 2, 4, 8, 16 and 30 seconds, and the message is marked failed after 2 minutes in total of available network time without success. Rate-limited sends wait the time the server gives, then retry.
- Messages the server rejects (muted, blocked, failed moderation and so on) are marked failed immediately.
- On failure the message's status becomes
failedandMessageSendFailedfires. Callim.messages.resend(clientMsgId)to resend orim.messages.discard(clientMsgId)to delete it; see Messages. - Retries of the same message use the same
client_msg_id, which the server uses to de-duplicate, so it's never sent twice.
Writes that aren't retried automatically (such as creating a group, sending a friend request or editing a group profile) may already have happened when they hit network_error or timeout. Check the result first (for example re-read the group list), and only let the user retry if it didn't take effect.
While offline, local data stays readable and the UI can keep showing it. Conversation and message operations that only change local data (such as drafts) take effect immediately; those that need the server return network_error after automatic retries; sent messages go into the send queue. The connection state is ConnectionOffline or ConnectionWaiting, and when the network returns (including switching between Wi-Fi and mobile data) the SDK reconnects and syncs changes made while offline.
API reference
im.events
All events.
Type: Stream<DREvent>, a broadcast stream.
im.on()
Subscribes to events by type, such as im.on<MessageReceived>().
| Parameter | Type | Required | Description |
|---|---|---|---|
T | extends DREvent | Yes (type parameter) | The event type; see All events |
Returns: Stream<T>, a broadcast stream. Subscribe with listen, and cancel with cancel() on the returned StreamSubscription; for a single event, use first.
im.auth.stateStream, im.connection.stateStream
Changes to the sign-in state and connection state.
Type: Stream<AuthState> and Stream<DRConnectionState>, broadcast streams that only emit changes after you subscribe; read the current values from im.auth.state and im.connection.state.
describeError()
Imported from package:deeprespond_im_flutter/render.dart. Gives message text for an error code.
| Parameter | Type | Required | Description |
|---|---|---|---|
err | DRException | Yes | The error |
locale | DRLocale | No (named) | The strings to use. Default zhCN |
Returns: String.
formatTemplate()
Imported from package:deeprespond_im_flutter/render.dart. Fills in {name} placeholders in a string, leaving placeholders without a value as they are. Useful when writing your own strings with placeholders.
| Parameter | Type | Required | Description |
|---|---|---|---|
template | String | Yes | The string, such as '你已被禁言,至 {time}' |
values | Map<String, String> | Yes | Values for the placeholders |
Returns: String.
Types
DRException
Implements Exception; see The error object for its fields. It also has:
| Member | Description |
|---|---|
DRException({required code, message, details, status, requestId, retryAfter, required source, cause}) | Constructor; message defaults to code |
toJson() | Converts to a Map (without cause), for passing through an isolate port or writing to logs |
DRException.fromJson(json) | Static method that restores one from the result of toJson() |
ErrorSource
Enum: http, ws, local.
LocalErrorCode
Constants for the SDK's own error codes: networkError, timeout, aborted, notSignedIn, notConnected, localValidation, storageError, unsupported, permissionRequired, deviceError, invalidState, sendAbandoned, plus permissionDenied and rateLimited, which mirror the server's codes locally. Values are the matching strings, such as 'network_error' for LocalErrorCode.networkError.
DREvent
The base class of all events (sealed); for each event's fields, see All events.
NotifyDecision
notify in MessageReceived, saying whether this message should alert.
| Field | Type | Description |
|---|---|---|
notify | bool | Whether to alert |
title | String? | Notification title, built from the push settings' preview mode |
body | String? | Notification body |
reason | String? | Why not to alert: dnd (do not disturb), quiet_hours, muted (conversation muted), not_mentioned (muted to mentions only, and this message doesn't mention the user), excluded (message doesn't count as unread), self (the user's own message), tip (group notice), call (call record), not_loaded (push settings not loaded yet) |
For full alert usage, see Alerts, do not disturb and reports.
DRLocale
A table of message strings; zhCN holds the built-in Chinese strings.
| Field | Type | Description |
|---|---|---|
errors | Map<String, String> | Error messages, keyed by code or code.reason |
mutedUntil | String | Mute message with an end time, using the placeholder {time} |
deletedUser, unnamedGroup, system, you | String | Display names for a deleted user, a group without a name, the system, and "you" |
summary | Map<String, String> | Last-message summaries in the conversation list |
recall | Map<String, String> | Recall notices |
tip | Map<String, String> | Group notices |
call | Map<String, String> | Call records |
copyWith(...) replaces entries: each table is merged by key, and keys you leave out keep their originals. The last four are used to display conversations and messages; see Conversations and Messages.
ChannelChanged and channel streams
ChannelChanged fires when this device's current channel changes; channel is the current channel handle, or null after leaving.
To keep showing a channel's state, subscribe to the handle's channel.stream (state snapshots) and channel.tracks (all audio and video tracks); both deliver the current value first when you subscribe. Don't rely on a single event to know who's in the channel. For an example and how to leave, see Standalone 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 |
|---|---|
| Flutter | Import en, zhTW and localeForLanguage(tag) from render.dart |
| Web | createLocale() and describeError() from @deeprespond/im-web/locale |
| Android | DRLocale.en, DRLocale.zhTW, DRLocale.forLanguage(tag) |
Pass the chosen table as locale to display helpers such as describeError; 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.
