事件與錯誤處理
Flutter SDK 用事件通知你資料和狀態的變化,用 DRException 報告所有的錯誤。本頁列出全部事件和它們的欄位、錯誤物件的結構、全部錯誤碼與處理建議,以及 SDK 在離線和出錯時自動做了什麼。
大多數介面不需要直接處理資料變化的事件:會話列表、好友列表等 LiveList 和訊息列表會自動更新,見在 Flutter 介面中使用。事件主要用於三類場景:登入和連線狀態的變化(回到登入頁、顯示“連線中”),提醒(新訊息通知、來電),以及讀取同步狀態的值(未讀總數、正在輸入等)時得知何時重新讀取。
| 要做什麼 | 看哪裡 |
|---|---|
| 訂閱和取消事件 | 訂閱與取消 |
| 查某個事件的欄位 | 全部事件 |
| 讓角標、狀態等同步讀取的值保持最新 | 用事件讀取同步狀態的值 |
| 判斷錯誤、查錯誤碼 | 錯誤物件、SDK 自己的錯誤碼、常見的服務端錯誤碼 |
| 給使用者顯示錯誤提示 | 顯示錯誤提示 |
| 瞭解 SDK 自動重試了什麼 | 離線與重試 |
訂閱與取消
事件是 Dart 的 Stream。im.on<T>() 只訂閱某一類事件,im.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}');- 每個事件是一個類,都繼承自
DREvent。DREvent是sealed類,訂閱im.events時可以用switch按型別處理(見下文)。 - 兩者都是廣播流,可以多處同時訂閱;訂閱之前發出的事件不會補發,需要當前值時先讀取一次(見用事件讀取同步狀態的值)。
- 事件在主 isolate 中發出。監聽函式丟擲的異常交給當前 Zone 的未捕獲錯誤處理(Flutter 中為
PlatformDispatcher.instance.onError),不影響其他監聽函式,也不影響 SDK。 - 資料變化的事件在本地資料更新之後發出,載荷只說明變了什麼(如哪些會話變了),你從對應的列表或方法讀取最新的資料。
- 同一個變化只發出一次:本人操作的結果和隨後服務端的通知會合並,不會收到重複的事件。
- 客戶端與程序同生命週期,沒有銷燬方法;在 Widget 中訂閱的,在
dispose()中取消,見在 Flutter 介面中使用。
登入狀態和連線狀態另有 im.auth.stateStream、im.connection.stateStream,直接給出新的狀態,與 AuthStateChanged、ConnectionStateChanged 相同。
一處處理多種事件:
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:
}
});全部事件
以下事件類都從 package:deeprespond_im_flutter/deeprespond_im_flutter.dart 匯出。
登入與連線
| 事件 | 欄位 | 說明 |
|---|---|---|
AuthStateChanged | state:AuthState | 登入狀態變化:SignedIn、SignedOut、SessionEnded(會話結束,帶 code、reason)、Suspended(服務暫停),見初始化、登入與連線 |
ConnectionStateChanged | state:DRConnectionState | 連線狀態變化:ConnectionIdle、ConnectionConnecting、ConnectionAuthenticating、ConnectionSyncing、ConnectionReady、ConnectionWaiting、ConnectionOffline、ConnectionStopped,kind 為對應的字串,見初始化、登入與連線 |
SdkUpgradeRequired | minVersion:String | SDK 版本低於服務端要求的最低版本,連線已停止,本地資料只讀,寫操作以 invalid_state(upgrade_required)拒絕。請提示使用者升級 App |
AppLifecycleReported | background:bool;badge:int? | App 切到後臺時,SDK 已向服務端報告前後臺狀態,badge 為報告的角標數(只在 iOS、Android 上發出) |
PushOpened | payload:PushPayload | 使用者點選了 IM 的通知;由點選通知冷啟動的,在建立客戶端後立即發出。需要 deeprespond_im_push,見離線推送 |
同步與本地資料
| 事件 | 欄位 | 說明 |
|---|---|---|
SyncCompleted | modules:List<SyncModule>;trigger:SyncTrigger | 一輪同步完成。modules 為這一輪同步的模組(config、me、friends、groups、messages、push、calls、chatrooms、presence);trigger 為同步的起因:login(登入後)、reconnect(重連後)、syncRequired(服務端要求全量同步)、hint(服務端提示有新資料)、foreground(回到前臺)、periodic(定期檢查)、fallback(長連線不可用時的定期同步) |
SyncFailed | module:SyncModule;error:DRException | 某個模組同步失敗,SDK 會自動重試,一般只需記錄日誌 |
StorageReset | reason:String;droppedMessages:int? | 本地資料已重置。reason:reset_required(服務端要求重置)、schema_downgrade(App 降級到舊版本後,本地資料庫刪除重建)、corrupted(本地資料庫無法開啟,刪除重建)、reloaded(本地資料已重新載入)。開啟著的列表已自動重新讀取,你只需重新讀取自己另外儲存的資料;droppedMessages 為因此丟棄的未發出訊息數 |
StorageFull | 無 | 寫入本地資料時磁碟已滿(10 分鐘內最多發出一次)。SDK 已刪除本機上其他使用者的本地資料;請提示使用者清理手機空間,也可以清除圖片快取(DRImageCache.instance.clear()) |
執行配置與本人
| 事件 | 欄位 | 說明 |
|---|---|---|
ConfigChanged | config:ClientConfig? | 應用的執行配置變化,與 im.config 相同;退出後為 null |
MeChanged | me:SelfProfile? | 本人資料變化,與 im.users.me() 相同;退出後為 null |
MeMuteChanged | scope:String;muted:bool;expiresAt:DateTime? | 本人被全域性禁言或解除禁言(本次連線期間收到的),scope 為 chat、group 或 room |
UsersChanged | usernames:List<String> | 其他使用者的資料快取更新,用 im.users.get() 顯示的介面應重新讀取 |
DevicesChanged | devices:List<OnlineDevice> | 本人的線上裝置變化,與 im.devices.online() 相同 |
DevicesNewLogin | sessionId、deviceName、platform、loginMethod:String;createdAt:DateTime? | 本人在一臺新裝置上登入,可以提示“你的賬號在 XX 上登入” |
會話與訊息
| 事件 | 欄位 | 說明 |
|---|---|---|
ConversationsChanged | upserted、removed:List<String> | 會話列表中的會話新增、變化或移除,值為會話鍵 conversationKey |
ConversationsUnreadChanged | total:int;source:String | 未讀總數變化,total 與 im.conversations.getUnreadTotal() 相同;source 為 local 或 server |
MessagesChanged | conversationKey:String;upserted、removed:List<int>;pending:List<String>;renamedFrom:String? | 某個會話的訊息變化:upserted、removed 為訊息的序號,pending 為傳送佇列中變化的 clientMsgId;renamedFrom 表示這個會話由草稿會話變為正式會話,值為原來的草稿鍵 |
MessageReceived | message:MessageView;notify:NotifyDecision | 收到別人發來的新訊息,用於提醒:notify 說明是否應該提醒(已考慮免打擾、勿擾時段等)。只在實時收到時發出,補齊的歷史訊息、重連後同步得到的離線期間的訊息不發出 |
MessageSendFailed | clientMsgId、conversationKey:String;error:DRException | 一條訊息最終傳送失敗(自動重試已用完,或被服務端拒絕)。訊息留在列表中,狀態為 failed,可以讓使用者重發或刪除 |
MessageOnline | message:OnlineMessage | 收到只推線上的訊息(sendOnline 發出的,如“對方正在操作”的自定義信令),不儲存 |
TypingChanged | conversationKey:String;usernames:List<String> | 會話中正在輸入的人變化 |
ReceiptsChanged | conversationId:String;seqs:List<int> | 群訊息的已讀人數變化,用 im.messages.getReceipt() 讀取 |
聯絡人與群組
| 事件 | 欄位 | 說明 |
|---|---|---|
FriendsChanged | upserted、removed:List<String> | 好友新增、資料或備註變化、刪除,值為使用者名稱 |
BlacklistChanged | upserted、removed:List<String> | 黑名單變化 |
FriendRequestsChanged | lists:List<String>;unreadCount:int | 收到的(received)或發出的(sent)好友申請變化,unreadCount 為未讀的收到的申請數 |
GroupsChanged | upserted、removed:List<String> | 我的群列表或群資訊變化,值為群 ID |
GroupMembersChanged | groupId:String;usernames:List<String>? | 群成員變化;usernames 為 null 表示整個成員列表變化 |
GroupRequestsChanged | list:String;groupId:String? | 入群請求變化:pending 為某個群待審批的請求(帶 groupId),invitations 為本人收到的入群邀請,applications 為本人的入群申請 |
推送設定與線上狀態
| 事件 | 欄位 | 說明 |
|---|---|---|
PushChanged | settings:bool;mutes:List<MuteTarget> | 推送設定(settings 為 true)或會話免打擾變化,mutes 為免打擾變化了的會話 |
PresenceChanged | items:List<PresenceState>;reset:bool;cleared:List<String> | 訂閱的人線上狀態變化;reset 為 true 表示本地的線上狀態已全部清除(如重新連線後),cleared 為被清除了狀態的人 |
PresenceSubscribeFailed | usernames:List<String>;reason:PresenceFailure? | 訂閱沒有生效的人:denied(沒有許可權檢視)、limitExceeded(超出訂閱上限)、presenceDisabled(應用沒有開啟線上狀態) |
音影片
| 事件 | 欄位 | 說明 |
|---|---|---|
CallIncoming | call:CallHandle | 來電開始振鈴。App 在前臺時由你顯示來電介面;在後臺時由 deeprespond_im_call 顯示系統來電介面 |
CallChanged | call:CallHandle;change:CallChange? | 通話的狀態、成員等變化;change 為 null 表示由本裝置自己的操作或重新同步引起 |
GroupCallChanged | groupId:String;banner:GroupCallBanner? | 群中正在進行的通話變化,用於顯示“群通話進行中”的橫幅;通話結束時橫幅的 status 為 ended,查詢到群裡沒有通話時 banner 為 null |
通話的詳細用法見音視訊通話。聊天室的訊息、成員進出等在進入聊天室得到的物件上訂閱,見聊天室。
用事件讀取同步狀態的值
以下值可以同步讀取,變化時發出對應的事件,“訂閱事件 + 讀取”就能讓介面保持最新:
| 讀取 | 變化時的事件 |
|---|---|
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 |
值沒有變化時,讀取返回同一個物件。登入、退出、切換使用者時,這些值也可能變化,對應的事件同樣會發出。
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();
},
);
}錯誤物件
SDK 的所有錯誤都是 DRException:返回 Future 的方法以它失敗,同步方法以它丟擲,MessageSendFailed、SyncFailed 等事件中也是它。
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}');
}| 欄位 | 型別 | 說明 |
|---|---|---|
code | String | 錯誤碼:服務端返回的錯誤碼,或 SDK 自己的錯誤碼(見下文) |
reason | String? | 原因,即 details['reason'] |
details | Map<String, Object?>? | 服務端返回的 details,原樣,不可修改;SDK 自己的錯誤中為補充資訊 |
status | int? | HTTP 狀態碼;長連線的錯誤和 SDK 自己的錯誤沒有 |
requestId | String? | 服務端的 request_id,聯絡技術支援時提供 |
retryAfter | int? | 建議的等待時間,單位為秒 |
source | ErrorSource | 錯誤的來源:ErrorSource.http(HTTP 請求)、ErrorSource.ws(長連線)、ErrorSource.local(SDK 本地) |
message | String | 除錯用的說明,不要直接顯示給使用者,提示文字見顯示錯誤提示 |
cause | Object? | 引起這個錯誤的原始異常(如 SocketException),只用於排查 |
請按 code 和 reason 判斷錯誤,不要按 status 或 message 判斷。SDK 自己的錯誤碼有常量,見 LocalErrorCode(如 LocalErrorCode.networkError)。
與服務端錯誤碼的關係
服務端返回的錯誤原樣交給你,code、details 與服務端文件相同,source 為 http 或 ws。各錯誤碼的含義和 details 的取值見服務端的錯誤碼,客戶端特有的錯誤碼(如 user_muted、group_muted、not_friend、user_blocked)見其中的控制檯和客戶端介面的錯誤碼。
SDK 在發出請求之前先在本地檢查,確定會被服務端拒絕的直接拒絕,source 為 local:
- 功能沒有開啟、沒有許可權的(如應用沒有開啟群已讀回執、普通成員 @所有人),沿用服務端的
permission_denied和原因,如read_ack_disabled、mention_all_denied、presence_disabled; - 超出服務端的固定頻率限制的(如“全部已讀”每分鐘 2 次),沿用
rate_limited,帶retryAfter; - 引數和內容不合規的(大小、格式、必填欄位、不支援的字元等),一律為
local_validation,details['reason']和details['field']說明原因。local_validation總是表示請求沒有發出。
這樣你對“功能不可用、沒有許可權”和“太頻繁”只需按服務端的錯誤碼處理一次。
SDK 自己的錯誤碼
code | 常量 | 含義 | 建議 |
|---|---|---|---|
network_error | networkError | 網路不可用或請求沒有送達 | 提示檢查網路,稍後重試 |
timeout | timeout | 請求超時 | 稍後重試;寫操作先確認結果,見離線與重試 |
aborted | aborted | 被你用 DRCancelToken 或 cancel() 取消 | 不提示 |
not_signed_in | notSignedIn | 未登入時呼叫了需要登入的方法 | 先登入 |
not_connected | notConnected | 必須走長連線的操作(如聊天室的部分操作)在未連線時呼叫 | 等連線就緒後再試 |
local_validation | localValidation | 本地檢查不透過,請求沒有發出。reason 為原因(如 required、too_large、control_characters、url_not_allowed),details['field'] 為欄位 | 提示使用者修改 |
storage_error | storageError | 讀寫本地資料出錯;reason 為 quota_exceeded 時是磁碟已滿(同時發出 StorageFull) | 提示清理手機空間,或重新開啟 App |
unsupported | unsupported | 當前平臺或部署不支援:reason 為 media_disabled(沒有開通檔案服務)、platform(如桌面端錄音)、sqlcipher_required(開啟了 encryptDatabase,但 App 沒有使用 SQLCipher 構建)、chatroom_attachment(聊天室不能傳送檔案)等 | 隱藏對應的功能,或修正配置 |
permission_required | permissionRequired | 使用者沒有授權使用麥克風或攝像頭,details['permissions'] 列出缺少的許可權(microphone、camera) | 引導使用者在系統設定中授權 |
device_error | deviceError | 麥克風或攝像頭不存在、被佔用或出錯,details['device'] 為 microphone 或 camera(通話中),reason 說明原因 | 提示檢查裝置 |
invalid_state | invalidState | 當前狀態下不能呼叫,reason 說明原因:disposed(列表已釋放)、signed_in(不能清除當前使用者的本地資料)、upgrade_required(SDK 版本過低,本地資料只讀)、recording、not_recording(錄音的狀態不對)等 | 一般是呼叫順序的問題,檢查程式碼 |
send_abandoned | sendAbandoned | SDK 放棄傳送一條訊息:reason 為 attachment_lost(附件已丟失:超過 OutboxOptions.persistAttachmentMaxBytes(預設 200 MB)的附件不復制,原檔案在發出前被刪除或移動)或 expired(超過 OutboxOptions.maxAge,預設 48 小時沒有發出) | 提示使用者重新傳送 |
local_validation 的原因
local_validation 的 details['reason'](即 reason)說明哪裡不合規,details['field'] 為欄位名(如 body.text、username):
reason | 含義 |
|---|---|
required | 必填的引數或欄位為空 |
invalid_format | 格式不對,如 appKey、apiUrl、client_msg_id |
invalid_value | 取值不在允許的範圍內,或檔案不存在 |
invalid_type | 引數的型別不對 |
out_of_range | 數值超出範圍 |
too_long | 文字太長 |
too_large | 內容或檔案太大,如訊息超過執行配置的 max_message_body_bytes、附件超過 max_upload_bytes |
too_many_items | 一次傳入的專案太多,如一次刪除超過 100 條訊息 |
control_characters | 文字中有不支援的控制字元(只有文字訊息的正文允許換行、回車和製表符) |
invalid_body、unknown_type | 訊息內容不符合訊息型別的要求;不支援的訊息型別 |
mentions_not_allowed、receipt_not_allowed | @ 提及、群已讀回執只能用於群聊 |
url_not_allowed | 應用開啟了只接受本服務的檔案地址,這個地址不符合要求 |
invalid_purpose | 這種檔案不能用於這裡(如用影片作頭像) |
image_too_large、unsupported_type | 檔案不能按請求的型別上傳,見檔案與圖片 |
invalid_ext、invalid_priority、invalid_attribute_value、attributes_too_large | 聊天室的擴充套件資訊、訊息優先順序、屬性不符合要求 |
too_many_invitees | 一次邀請通話的人太多 |
invalid_evidence、self_report | 舉報的證據訊息不符合要求;不能舉報自己 |
not_found | 本地找不到要操作的會話、訊息或檔案(如重發一條已不在傳送佇列中的訊息) |
describeError 對其中常見的原因給出了專門的提示,其餘顯示“內容不符合要求”。
常見的服務端錯誤碼
code | 含義 | 建議 |
|---|---|---|
invalid_argument | 引數不符合服務端的規則 | 提示使用者修改;reason 說明原因 |
unauthenticated、session_revoked | 登入失效 | SDK 已自動處理(續期或結束會話),你只需處理 AuthStateChanged 中的 SessionEnded |
invalid_credentials | 使用者名稱、密碼或憑證錯誤 | 見初始化、登入與連線 |
permission_denied | 沒有許可權,或功能沒有開啟,reason 說明原因 | 按原因提示,或隱藏對應的功能 |
not_found | 使用者、群、會話、訊息等不存在或已刪除 | 提示“內容不存在或已刪除” |
already_exists | 要建立的已存在 | 視為成功或提示 |
version_conflict | 資料已被修改 | 重新讀取後再提交 |
limit_exceeded | 超出數量上限,reason 說明是哪一項 | 提示已達上限 |
payload_too_large | 內容或檔案太大 | 提示使用者縮短內容或換小一些的檔案 |
rate_limited | 操作太頻繁 | 按 retryAfter 稍後再試;SDK 已對可以安全重試的請求自動等待重試 |
too_many_attempts | 密碼錯誤次數過多 | 按 retryAfter 提示稍後再試 |
message_rejected、content_rejected | 訊息或資料沒有透過內容安全檢查 | 提示使用者修改內容;reason 為 app_rejected 時,message 是你的服務端給出的提示,可以直接顯示 |
user_muted、group_muted | 本人被全域性禁言、在群中被禁言或群開啟了全員禁言 | 提示禁言,details['expires_at'] 為到期時間 |
user_blocked | 被對方拉黑 | 提示“對方拒收了你的訊息” |
not_friend | 應用要求只能給好友發訊息,雙方不是好友 | 提示先加好友 |
not_group_member | 本人已不在群中 | 提示並重新整理介面 |
tenant_unavailable、app_unavailable | 服務暫停或只讀 | 提示“服務暫時不可用”,SDK 進入 Suspended 狀態,恢復後自動繼續 |
internal | 服務端故障 | 稍後重試,持續出現時帶上 requestId 聯絡技術支援 |
顯示錯誤提示
package:deeprespond_im_flutter/render.dart 中的 describeError 按 code 和 reason 給出中文的提示文字,如“傳送太頻繁,請稍後再試”“群主已開啟全員禁言”“你已被禁言,至 10 月 5 日 18:00”:
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));
}查詢順序為先找 code.reason(如 permission_denied.mention_all_denied),再找 code,都沒有時顯示通用的“伺服器繁忙,請稍後重試”。例外:message_rejected、permission_denied 的原因為 app_rejected 時,顯示服務端返回的 message(你的服務端給出的提示)。禁言(user_muted)帶到期時間的,按本地時間顯示到期時間。
要修改部分文案,或換成其他語言,傳入 locale。內建的中文文案是 zhCN,用 copyWith 替換其中的條目,文案表 errors 的鍵就是上面說的 code 或 code.reason,沒給的鍵沿用原來的:
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}'); // 内置的全部条目會話結束(SessionEnded)的原因也可以這樣顯示:describeError(DRException(code: ended.code, details: {'reason': ended.reason}, source: ErrorSource.http))。
離線與重試
SDK 按錯誤的型別自動處理,你只需處理最終返回的錯誤:
| 類別 | 錯誤碼 | SDK 的處理 |
|---|---|---|
| 登入失效 | unauthenticated、session_revoked | 令牌過期的自動續期並重發一次請求;會話被吊銷的結束會話(SessionEnded) |
| 服務暫停或只讀 | tenant_unavailable、app_unavailable | 返回給你;確認是服務暫停的,進入 Suspended 狀態,恢復後自動繼續 |
| 限流 | rate_limited、too_many_attempts | 等待時間不超過 60 秒的,等待後自動重試;更長的返回給你。傳送好友申請、建群、邀請入群、申請入群、發起和邀請通話、舉報、聊天室發訊息,以及 too_many_attempts,不自動重試 |
| 暫時故障 | network_error、timeout、internal、HTTP 5xx | 可以安全重發的請求(查詢、帶冪等鍵的寫操作、本身冪等的操作如標記已讀)按退避自動重試,最多 3 次;其他寫操作不自動重試,返回給你 |
| 需要使用者處理 | 其餘錯誤碼 | 返回給你,按 code 和 details 提示 |
訊息的傳送另有傳送佇列:
im.messages.send()把訊息寫入傳送佇列後就返回,之後由 SDK 傳送。斷網時訊息在佇列中等待,網路恢復後自動發出;App 被殺後,下次啟動並登入時繼續傳送。- 傳送遇到網路錯誤、超時、服務端故障時,按 1、2、4、8、16、30 秒的間隔重試,網路可用的時間累計 2 分鐘仍未成功的標為失敗;被限流的按服務端給出的時間等待後重試。
- 被服務端拒絕(如被禁言、被拉黑、內容未透過稽核)的立即標為失敗。
- 失敗時訊息狀態變為
failed,併發出MessageSendFailed。可以呼叫im.messages.resend(clientMsgId)重發,或im.messages.discard(clientMsgId)刪除,見訊息。 - 同一條訊息重試時使用同一個
client_msg_id,服務端據此去重,不會發出兩條。
沒有自動重試的寫操作(如建群、傳送好友申請、修改群資料)遇到 network_error、timeout 時,請求可能已經執行。請先查詢結果(如重新讀取群列表),確認沒有生效再讓使用者重試。
離線時:本地資料照常可讀,介面可以繼續顯示;會話和訊息的操作中只修改本地的(如草稿)立即生效,需要請求服務端的在自動重試後返回 network_error,傳送訊息進入傳送佇列。連線狀態為 ConnectionOffline 或 ConnectionWaiting,網路恢復(包括在 Wi-Fi 與行動網路之間切換)後 SDK 自動重連並同步離線期間的變化。
介面參考
im.events
全部事件。
型別:Stream<DREvent>,廣播流。
im.on()
按型別訂閱事件,如 im.on<MessageReceived>()。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
T | extends DREvent | 是(型別引數) | 事件的型別,見全部事件 |
返回值:Stream<T>,廣播流。用 listen 訂閱,返回的 StreamSubscription 的 cancel() 取消訂閱;只要一次時用 first。
im.auth.stateStream、im.connection.stateStream
登入狀態、連線狀態的變化。
型別:Stream<AuthState>、Stream<DRConnectionState>,廣播流,只發出訂閱之後的變化;當前的值用 im.auth.state、im.connection.state 讀取。
describeError()
從 package:deeprespond_im_flutter/render.dart 匯入。按錯誤碼給出提示文字。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
err | DRException | 是 | 錯誤 |
locale | DRLocale | 否(命名引數) | 文案,預設 zhCN |
返回值:String。
formatTemplate()
從 package:deeprespond_im_flutter/render.dart 匯入。填入文案中的佔位符 {name},沒有給出的佔位符原樣保留。自己編寫含佔位符的文案時可以用它。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
template | String | 是 | 文案,如 '你已被禁言,至 {time}' |
values | Map<String, String> | 是 | 佔位符的值 |
返回值:String。
資料結構
DRException
實現 Exception,欄位見錯誤物件。另有:
| 成員 | 說明 |
|---|---|
DRException({required code, message, details, status, requestId, retryAfter, required source, cause}) | 建構函式;message 省略時與 code 相同 |
toJson() | 轉為 Map(不含 cause),可以經 isolate 埠傳遞或寫入日誌 |
DRException.fromJson(json) | 靜態方法,從 toJson() 的結果還原 |
ErrorSource
列舉:http、ws、local。
LocalErrorCode
SDK 自己的錯誤碼的常量:networkError、timeout、aborted、notSignedIn、notConnected、localValidation、storageError、unsupported、permissionRequired、deviceError、invalidState、sendAbandoned,以及本地照搬服務端的 permissionDenied、rateLimited。值為對應的字串,如 LocalErrorCode.networkError 為 'network_error'。
DREvent
全部事件的基類(sealed),各事件的欄位見全部事件。
NotifyDecision
MessageReceived 的 notify,說明這條訊息是否應該提醒。
| 欄位 | 型別 | 說明 |
|---|---|---|
notify | bool | 是否應該提醒 |
title | String? | 通知的標題,按推送設定的預覽方式生成 |
body | String? | 通知的內容 |
reason | String? | 不提醒的原因:dnd(勿擾中)、quiet_hours(勿擾時段)、muted(會話免打擾)、not_mentioned(免打擾為只在 @ 本人時提醒,而這條沒有 @ 本人)、excluded(訊息不計未讀)、self(本人發的)、tip(群提示)、call(通話記錄)、not_loaded(推送設定還沒有載入) |
提醒的完整用法見提醒、免打擾與舉報。
DRLocale
提示文案的表,zhCN 為內建的中文文案。
| 欄位 | 型別 | 說明 |
|---|---|---|
errors | Map<String, String> | 錯誤的提示,鍵為 code 或 code.reason |
mutedUntil | String | 帶到期時間的禁言提示,佔位符 {time} |
deletedUser、unnamedGroup、system、you | String | 已刪除的使用者、沒有名稱的群、系統、“你”的顯示名稱 |
summary | Map<String, String> | 會話列表中最後一條訊息的摘要 |
recall | Map<String, String> | 撤回提示 |
tip | Map<String, String> | 群提示 |
call | Map<String, String> | 通話記錄 |
copyWith(...) 替換其中的條目:各個表按鍵合併,沒給的鍵沿用原來的。後四項用於會話和訊息的顯示,見會話和訊息。
ChannelChanged 與頻道流
本機的當前頻道變化時發出 ChannelChanged,channel 為當前的頻道控制代碼,離開後為 null。
要持續顯示頻道的狀態,訂閱控制代碼的 channel.stream(狀態快照)和 channel.tracks(全部音影片軌道),訂閱時都會先收到一次當前的值。不要只根據一次事件判斷頻道里有哪些成員。示例和離開時的處理見獨立頻道。
簡體、繁體與英語提示
SDK 自帶簡體中文、繁體中文和英語的提示文字,預設簡體中文,不呼叫線上翻譯:
| 平臺 | 用法 |
|---|---|
| Flutter | 從 render.dart 匯入 en、zhTW、localeForLanguage(tag) |
| Web | @deeprespond/im-web/locale 的 createLocale()、describeError() |
| Android | DRLocale.en、DRLocale.zhTW、DRLocale.forLanguage(tag) |
把選好的文案表作為 locale 交給 describeError 等顯示輔助函式即可,使用者選擇的語言由你的 App 儲存。切換語言只改變提示文字,錯誤碼和使用者發的內容不變。
服務端的錯誤可能帶 message_key 和 message_params(用於生成本地化的提示),判斷錯誤時仍然用 code、reason 和 details。詳細的入口和語言的選擇規則見語言與本地化。
