使用者、好友與線上狀態
本頁介紹與“人”有關的功能:本人的資料、其他使用者的資料、好友和好友申請、黑名單、線上狀態,以及本人的登入裝置。
- 使用者資料(
im.users):本人資料隨登入取得並儲存在本地,可以修改暱稱、頭像和自定義屬性;其他使用者的資料按需批次獲取並快取在本地資料庫中,列表介面直接同步讀取。 - 好友(
im.friends):好友列表和黑名單完整儲存在本地,以可訂閱的列表(LiveList)提供,其他裝置上的變化實時同步過來;好友申請按頁查詢。 - 線上狀態(
im.presence):訂閱要顯示的人,狀態變化時收到事件;也可以查詢一次。 - 登入裝置(
im.devices):檢視本人當前線上的裝置和全部登入裝置,踢掉其他裝置。
這些方法都要在登入之後呼叫(見初始化、登入與連線)。可訂閱的列表、同步讀取的狀態和事件的通用規則見事件與錯誤處理,在 Widget 中使用列表見在 Flutter 介面中使用。
| 要做什麼 | 看哪裡 |
|---|---|
| 顯示和修改本人的暱稱、頭像 | 本人資料 |
| 在列表中顯示別人的暱稱和頭像 | 在列表中顯示暱稱和頭像 |
| 顯示好友列表、判斷是不是好友 | 顯示好友列表、檢查與某人的關係 |
| 加好友、處理“新朋友” | 傳送申請、收到的申請和未讀數 |
| 拉黑 | 黑名單 |
| 顯示線上、離線 | 訂閱要顯示的人 |
| 檢視和踢掉本人的其他登入裝置 | 本人的登入裝置 |
本人資料
讀取本人資料
im.users.me() 同步返回本地儲存的本人資料,未登入時為 null。資料在其他裝置上被修改、或被你的服務端修改時,SDK 自動更新本地的資料併發出 MeChanged 事件。
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
final me = im.users.me();
print(me == null ? '未登录' : (me.nickname.isNotEmpty ? me.nickname : me.username));
final sub = im.on<MeChanged>().listen((e) {
final profile = e.me;
if (profile != null) print('资料已更新:${profile.nickname}');
});
// 不再需要时 sub.cancel()需要確認拿到的是服務端最新的資料時,呼叫 im.users.refreshMe()。
修改暱稱、頭像和自定義屬性
im.users.updateMe() 只提交傳入的引數,沒有傳的保持不變。清空暱稱或頭像時傳空字串;自定義屬性按項合併,值為 null 的鍵被刪除;要清空全部自定義屬性,傳 clearAttributes: true。修改成功後本地資料隨即更新,併發出 MeChanged。
final updated = await im.users.updateMe(
nickname: '张三',
attributes: {'sign': '今天也要加油', 'city': null}, // 设置 sign,删除 city
);
print(updated.version);暱稱和頭像要經過內容安全檢查,不透過時以 content_rejected 拒絕,details['field'] 為 nickname 或 avatar_url。各欄位的格式要求見服務端的使用者管理。
多臺裝置可能同時修改資料時,可以帶上讀取到的 version,資料已被別處修改時以 version_conflict 拒絕,不做任何修改:
final current = im.users.me();
try {
await im.users.updateMe(nickname: '张三', version: current?.version);
} on DRException catch (e) {
if (e.code == 'version_conflict') {
await im.users.refreshMe(); // 取得最新的资料,让用户重新确认
}
}設定頭像
頭像要先以用途 user_avatar 上傳,得到公開地址後再寫入資料。頭像圖片最大 5 MB,上傳和圖片處理的細節見檔案與圖片。
Future<void> changeAvatar(String path) async {
final info = await im.files.upload(DRFile(path), purpose: UploadPurpose.userAvatar).result;
await im.users.updateMe(avatarUrl: info.url);
}應用在執行策略中開啟了 media_url_only 時,只能使用本應用上傳的頭像地址(或允許的主機的 https 地址),其他地址以 invalid_argument 拒絕,details['reason'] 為 url_not_allowed。
本人是否被禁言
你的服務端可以對使用者全域性禁言。im.users.myMute 給出本人在單聊(chat)、群聊(group)、聊天室(room)三個範圍的禁言,沒有禁言的為 null,變化時發出 MeMuteChanged:
im.on<MeMuteChanged>().listen((e) {
if (e.muted) {
final until = e.expiresAt;
showToast(until == null ? '你已被禁言' : '你已被禁言,到 ${until.toLocal()} 解除');
}
});只用於顯示
服務端沒有查詢本人禁言的介面,myMute 只反映本次執行期間收到的變化:App 重新啟動之後它為空,即使使用者仍在禁言中。不要據此禁用輸入框;傳送時以服務端返回的 user_muted 錯誤為準。
其他使用者的資料
在列表中顯示暱稱和頭像
im.users.get(username) 同步返回快取中的資料。快取中沒有或已過期(預設 10 分鐘,可用建立選項 DRClientOptions.userCacheTtl 修改)時,它返回已有的值(可能是 null),並在後臺獲取;獲取到之後發出 UsersChanged,事件中列出資料有變化的使用者名稱。
會話列表、成員列表這類一次顯示很多人的介面,應該用 get 加 UsersChanged 重新構建,不要逐個 await。50 毫秒內的多次後臺獲取會合併成一次請求,每次最多 100 人。
import 'dart:async';
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
import 'package:flutter/material.dart';
class UserName extends StatefulWidget {
const UserName({super.key, required this.im, required this.username});
final DRClient im;
final String username;
@override
State<UserName> createState() => _UserNameState();
}
class _UserNameState extends State<UserName> {
late final StreamSubscription<UsersChanged> _sub;
@override
void initState() {
super.initState();
_sub = widget.im.on<UsersChanged>().listen((e) {
if (e.usernames.contains(widget.username.toLowerCase())) setState(() {});
});
}
@override
void dispose() {
unawaited(_sub.cancel());
super.dispose();
}
@override
Widget build(BuildContext context) {
final profile = widget.im.users.get(widget.username);
return Text(profile?.displayName ?? widget.username);
}
}要按“好友備註、群暱稱、暱稱、使用者名稱”的順序顯示名字,可以用 package:deeprespond_im_flutter/render.dart 中的 displayName(im, username),它同樣同步讀取,資料到達後隨事件更新:
import 'package:deeprespond_im_flutter/render.dart';
final name = displayName(im, 'lisi');
final inGroup = displayName(im, 'lisi', groupId: '1840012345678901'); // 群里优先显示群昵称查詢資料
確實需要等待結果時(如開啟某人的資料頁),呼叫 im.users.fetch()。結果是以小寫使用者名稱為鍵的 Map,不存在或已刪除的使用者不在結果中。快取未過期的直接返回快取,force: true 時一律向服務端查詢。
final profiles = await im.users.fetch(['lisi', 'wangwu']);
final lisi = profiles['lisi'];
if (lisi == null) showToast('用户不存在');資料快取儲存在本地資料庫中,App 重新啟動後仍然有效。好友修改資料時服務端不推送,所以別人的資料最多在快取時間之後才更新。
好友
顯示好友列表
im.friends.list() 返回好友的可訂閱列表(LiveList<Friend>),包含全部好友,按備註、暱稱、使用者名稱中第一個非空的排序。登入後 SDK 自動同步好友列表,之後好友的增刪改(包括在其他裝置上的操作)都實時反映到列表中。列表不再顯示時呼叫 dispose()。
在 Widget 中用 deeprespond_im_flutter 的 LiveListBuilder 顯示:
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
import 'package:flutter/material.dart';
class FriendListPage extends StatefulWidget {
const FriendListPage({super.key, required this.im});
final DRClient im;
@override
State<FriendListPage> createState() => _FriendListPageState();
}
class _FriendListPageState extends State<FriendListPage> {
late final LiveList<Friend> _friends = widget.im.friends.list();
@override
void dispose() {
_friends.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => LiveListBuilder<Friend>(
list: _friends,
builder: (context, snapshot) {
if (snapshot.loading && snapshot.items.isEmpty) return const Center(child: CircularProgressIndicator());
return ListView.builder(
itemCount: snapshot.items.length,
itemBuilder: (context, i) => ListTile(title: Text(snapshot.items[i].displayName)),
);
},
);
}不在 Widget 中時,直接讀 snapshot 或監聽 stream:
final friends = im.friends.list();
final sub = friends.stream.listen((s) {
if (!s.loading) print(s.items.map((f) => f.displayName).join('、'));
});
// 不再需要时
await sub.cancel();
friends.dispose();只需要判斷某人是不是好友,或讀取他的備註時,用同步的 im.friends.get(username),不是好友時為 null。im.friends.listInfo 給出好友數和上限:
final info = im.friends.listInfo;
if (info != null) print('好友 ${info.count} / ${info.maxCount}');檢查與某人的關係
在使用者資料頁上顯示“加好友”“等待驗證”“已拉黑”等狀態時,用 im.friends.check(),一次最多 100 人。本人和不存在的使用者不在結果中。
final result = await im.friends.check(['lisi']);
final relation = result.isEmpty ? null : result.first;
if (relation?.isFriend == true) {
print('发消息');
} else if (relation?.request == 'sent') {
print('等待验证');
} else if (relation?.request == 'received') {
print('对方已向你发出申请');
} else {
print('加好友');
}blocked 只表示本人是否拉黑了對方;對方是否拉黑了本人不會告訴你。
設定備註和自定義屬性
備註和自定義屬性只屬於本人這一側,對方看不到。自定義屬性可以用來實現星標好友、好友分組;按項合併,值為 null 的鍵被刪除,clearAttributes: true 清空全部。
await im.friends.update('lisi', remark: '老李', attributes: {'star': '1', 'group': '同事'});刪除好友
刪除後雙方都不再是好友,對方的好友列表也會同步移除本人。刪除本來就不是好友的人同樣成功。
await im.friends.remove('lisi');好友申請
傳送申請
final result = await im.friends.requests.send(
username: 'lisi',
message: '我是张三,项目组的',
remark: '李四', // 成为好友后自动设置的备注
addSource: 'search', // 添加来源,由你定义,会展示给对方
);
if (result.accepted) {
showToast('已成为好友');
} else {
showToast('已发送,等待对方验证');
}結果取決於對方的加好友方式:對方需要驗證時 status 為 pending;對方允許任何人新增,或對方此前已向本人發出了申請時,雙方直接成為好友,status 為 accepted,friend 為新好友;對方拒絕任何人新增時以 friend_add_denied 拒絕。
常見的錯誤:
code | 說明 |
|---|---|
already_exists | 已經是好友。SDK 按成功處理,返回 status 為 accepted,不會以這個錯誤拒絕 |
friend_add_denied | 對方設定了拒絕任何人新增 |
user_blocked | 雙方有一方把另一方拉黑了 |
limit_exceeded | 本人的好友數已滿,或對方允許任何人新增而對方的好友數已滿 |
rate_limited | 傳送過於頻繁,details['reason'] 為 request_rate(每分鐘 20 個、每天 500 個)、pair_daily_limit(對同一個人每天 5 次)或 declined_cooldown(被拒絕後 24 小時內) |
content_rejected | 附言或新增來源未透過內容安全檢查 |
permission_denied | 你的服務端透過加好友前回撥拒絕了這次申請,details['reason'] 為 app_rejected 或 callback_unavailable |
timeout | 網路錯誤或超時,SDK 查詢後確認申請沒有發出 |
傳送申請不會自動重發:網路錯誤或超時後,SDK 先查詢雙方的關係,已經成為好友或申請已經發出的按成功返回;確認沒有發出的以 timeout 拒絕,由使用者決定是否再發。遇到 rate_limited 時請按 details['reason'] 提示使用者。
申請發出後不能撤回。申請的完整規則(有效期 30 天、重複申請會更新原來的申請等)見服務端的好友申請。
收到的申請和未讀數
im.friends.requests.unreadCount 同步給出收到的、還沒有檢視過的申請數(最多統計到 100,超過 99 時可以顯示“99+”),變化時發出 FriendRequestsChanged:
im.on<FriendRequestsChanged>().listen((e) {
final n = e.unreadCount;
print(n > 99 ? '99+' : (n == 0 ? '' : '$n'));
});開啟“新朋友”頁面時查詢收到的申請,展示之後標記為已讀:
final page = await im.friends.requests.received();
for (final r in page.items) {
print('${r.nickname.isNotEmpty ? r.nickname : r.username}:${r.message}(${r.status?.name})');
}
await im.friends.requests.markRead();- 不帶
cursor、status且limit不大於已快取的條數時,返回 SDK 記憶體中的第一頁,不發請求。SDK 在登入、重連和收到申請相關的通知時會重新整理這一頁,所以它是最新的。帶status篩選或翻頁時向服務端查詢。 markRead()不帶引數時,把當前第一頁中未讀的申請標為已讀;也可以傳入readUntil(已展示的最新一條申請的requestedAt)。
發出的申請用 im.friends.requests.sent() 查詢,規則相同。申請在最近一次傳送後 30 天內未處理即過期,狀態變為 expired。
同意或拒絕
final friend = await im.friends.requests.accept('wangwu', remark: '王五');
await im.friends.requests.decline('zhaoliu');同意後雙方成為好友,好友列表隨即更新;拒絕後對方 24 小時內不能再向本人申請。申請已過期時以 not_found 拒絕。同意時雙方任何一方的好友數已滿,以 limit_exceeded 拒絕。
黑名單
把某人加入黑名單後,他不能給本人發單聊訊息、不能向本人發好友申請、不能邀請本人入群;本人發給他的、尚未處理的好友申請被一併撤回。拉黑不解除好友關係,也不通知對方。詳見服務端的黑名單。
await im.friends.blacklist.add('spammer');
if (im.friends.blacklist.has('spammer')) print('已拉黑');
final blacklist = im.friends.blacklist.list(); // 按拉黑时间从新到旧
final sub = blacklist.stream.listen((s) => print(s.items.map((e) => e.username)));
await im.friends.blacklist.remove('spammer');黑名單最多 1000 人。應用在執行策略中關閉了黑名單攔截(user_blacklist_enabled)時,不能再拉黑別人,add 以 permission_denied 拒絕;檢視和移出照常可用。可以讀取 im.config?.userBlacklistEnabled 決定是否顯示“拉黑”入口。
加好友方式
每個使用者可以設定別人向自己傳送好友申請時怎樣處理:
FriendAddMode | 服務端的取值 | 說明 |
|---|---|---|
needConfirm | need_confirm | 需要驗證:生成待處理的申請,本人同意後成為好友 |
allowAny | allow_any | 允許任何人新增:不需要本人同意,直接成為好友 |
denyAny | deny_any | 拒絕任何人新增:對方的申請以 friend_add_denied 失敗 |
final settings = await im.friends.settings.get();
print(settings.friendAddMode == null ? '跟随应用默认(${settings.effectiveFriendAddMode?.name})' : settings.friendAddMode!.name);
await im.friends.settings.set(FriendAddMode.needConfirm);
await im.friends.settings.set(null); // 清除本人的设置,改用应用的默认值使用者沒有設定過時使用應用執行策略中的預設值(預設 need_confirm)。
線上狀態
線上狀態需要應用在執行策略中開啟 presence_enabled(預設關閉)。執行策略還決定能看到誰的狀態:presence_scope 為 friends(預設)時只能看到好友的,為 all 時可以看到任何使用者的;presence_last_seen_visible 關閉後看不到最近離線時間(lastSeenAt 為 null)。可以從 im.config 讀取這幾項,決定介面上是否顯示線上狀態:
final config = im.config;
final showPresence = config?.presenceEnabled == true;
final scope = config?.presenceScope; // friends 或 all關閉了線上狀態時,subscribe 同步丟擲、query 以同樣的錯誤拒絕:permission_denied(details['reason'] 為 presence_disabled)。
訂閱要顯示的人
im.presence.subscribe() 登記要持續關注的人,返回取消這次訂閱的函式。狀態變化時發出 PresenceChanged,之後用同步的 im.presence.get(username) 讀取:
import 'dart:async';
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
import 'package:flutter/material.dart';
class OnlineDot extends StatefulWidget {
const OnlineDot({super.key, required this.im, required this.username});
final DRClient im;
final String username;
@override
State<OnlineDot> createState() => _OnlineDotState();
}
class _OnlineDotState extends State<OnlineDot> {
late final void Function() _unsubscribe;
late final StreamSubscription<PresenceChanged> _sub;
@override
void initState() {
super.initState();
_unsubscribe = widget.im.presence.subscribe([widget.username]);
_sub = widget.im.on<PresenceChanged>().listen((_) => setState(() {}));
}
@override
void dispose() {
unawaited(_sub.cancel());
_unsubscribe();
super.dispose();
}
@override
Widget build(BuildContext context) {
final state = widget.im.presence.get(widget.username);
final color = state == null ? Colors.grey.shade300 : (state.online ? Colors.green : Colors.grey);
return Icon(Icons.circle, size: 10, color: color);
}
}- 按需訂閱:只訂閱當前看得到的人,如會話列表中可見的好友、開啟的單聊對方。同一個人被訂閱多次時計數,全部取消後才真正退訂。
- 延遲:訂閱的變化合並 2 秒後才發給服務端,取消後 30 秒才退訂(期間又需要的不退訂),所以快速滾動列表不會產生大量請求。
- 上限:一臺裝置最多同時訂閱 3000 人。
- 重新連線:斷線重連後 SDK 自動重新訂閱,期間
PresenceChanged的reset為true,表示之前的狀態都已清除。
get 在以下情況返回 null,介面應顯示為“未知”而不是“離線”:還沒有收到訂閱結果;對方不存在或看不到;重新連線後還沒有重新訂閱完;presence_scope 為 friends 而對方已不是好友(這時 PresenceChanged 的 cleared 中列出他)。
沒能訂閱的人以 PresenceSubscribeFailed 事件報告:
im.on<PresenceSubscribeFailed>().listen((e) {
// reason:denied(不存在或看不到)、limitExceeded(超出上限)、presenceDisabled(应用关闭了在线状态)
print('在线状态订阅失败 ${e.reason?.wire} ${e.usernames}');
});服務端的限流(訂閱按使用者每分鐘 30 次)由 SDK 等待後自動重試,不報事件。
前後臺對線上狀態的影響
線上的含義是“至少有一臺裝置連著 IM 服務”,切到後臺的裝置也算線上。移動端(iOS、Android)切換前後臺時:
- 切到後臺:SDK 向服務端報告進入後臺,長連線保持,心跳間隔變長。只要連線還在,其他人看到的仍是線上(平臺列表中仍有
ios或android),本裝置訂閱的線上狀態也照常更新。 - 系統斷開連線之後:系統掛起 App 或斷開網路後,連線關閉,服務端隨即把這臺裝置記為離線(如果使用者沒有其他線上裝置,其他人看到他離線);連線異常中斷、沒有正常關閉時,服務端要在後臺的空閒超時(最長 360 秒)之後才發現。後臺期間 SDK 不主動重連,本裝置訂閱的狀態不再更新,
get返回的是斷開前的值。 - 回到前臺:SDK 立即重新連線,其他人看到本人重新上線;本裝置先發出
reset為true的PresenceChanged(之前的狀態全部清除),再重新訂閱,收到結果後再次發出PresenceChanged。
所以 App 不需要在前後臺切換時自己取消、恢復訂閱:保留各頁面的訂閱即可,回到前臺後狀態自動重新整理。桌面端(macOS、Windows、Linux)不區分前後臺,連線一直按前臺處理。
網路在 Wi-Fi 與行動網路之間切換時,SDK 立即建立新連線,新連線替換舊連線,其他人看到的線上狀態不會先離線再上線。
查詢一次
不需要持續更新時(如開啟資料頁時顯示一次),用 im.presence.query() 查詢,一次最多 100 人。查詢的結果不會更新 get 讀到的狀態。
final states = await im.presence.query(['lisi']);
final state = states.isEmpty ? null : states.first;
final lastSeen = state?.lastSeenAt;
if (state != null && !state.online && lastSeen != null) {
print('最近在线:${lastSeen.toLocal()}');
}離線的判定有延遲:裝置異常斷網時,服務端要在連線空閒超時之後才判定離線,見服務端的線上狀態。
本人的登入裝置
當前線上的裝置
im.devices.online() 同步返回本人當前保持著連線的裝置(包括這臺),變化時發出 DevicesChanged,可以用來顯示“電腦已登入”:
im.on<DevicesChanged>().listen((e) {
final others = e.devices.where((d) => !d.current).toList();
print(others.isEmpty ? '' : '${others.map((d) => d.platform).join('、')} 已登录');
});本人在其他裝置上登入時發出 DevicesNewLogin,可以提示使用者:
im.on<DevicesNewLogin>().listen((e) {
final name = e.deviceName.isNotEmpty ? e.deviceName : e.platform;
showToast('你的账号于 ${e.createdAt?.toLocal()} 在 $name 上登录');
});全部登入裝置與踢下線
im.devices.sessions() 返回本人全部有效的登入(包括目前沒有連線的裝置),按最近活躍時間從新到舊,current 標出這臺裝置。im.devices.kick() 讓指定的裝置下線:
final sessions = await im.devices.sessions();
for (final s in sessions) {
print('${s.deviceName ?? s.platform}${s.current ? '(本机)' : ''}');
}
final others = sessions.where((s) => !s.current);
if (others.isNotEmpty) await im.devices.kick(others.first.sessionId);被踢的裝置立即下線,需要重新登入。指定的是當前裝置時等同於退出登入。同時登入的裝置數受執行策略的限制,超出時最早的裝置會被擠下線,見服務端的登入與憑證。
deviceName 預設取系統提供的名稱(iOS 16 起只能取到“iPhone”這樣的通用名稱),可以在建立客戶端時用 DRClientOptions.deviceName 指定,見初始化、登入與連線。
介面參考
im.users.me()
同步返回本地儲存的本人資料。
返回值:SelfProfile?,未登入時為 null。變化時發出 MeChanged。
im.users.refreshMe()
向服務端獲取本人資料,更新本地儲存的資料。
返回值:Future<SelfProfile>。
im.users.updateMe()
修改本人資料,只提交傳入的引數。引數都是命名引數。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
nickname | String? | 否 | 暱稱,最長 64 個字元;空字串表示清空 |
avatarUrl | String? | 否 | 頭像地址;空字串表示清空 |
attributes | Map<String, String?>? | 否 | 自定義屬性,按項合併:值為 null 的鍵被刪除。最多 32 項,合計不超過 4 KB |
clearAttributes | bool | 否 | 為 true 時清空全部自定義屬性(忽略 attributes),預設 false |
version | int? | 否 | 讀取到的資料版本號,與服務端不一致時不修改 |
返回值:Future<SelfProfile>,修改後的資料。
可能的錯誤:
code | 說明 |
|---|---|
invalid_argument | 欄位格式不對;頭像地址不被允許(details['reason'] 為 url_not_allowed) |
content_rejected | 暱稱或頭像未透過內容安全檢查 |
version_conflict | version 與服務端不一致 |
im.users.myMute
只讀屬性:MyMute,本人在三個範圍的全域性禁言 chat、group、room,每項為 GlobalMuteInfo?。只反映本次執行期間收到的變化,變化時發出 MeMuteChanged。
im.users.get()
同步返回快取中的使用者資料;沒有或過期時在後臺獲取,獲取後發出 UsersChanged。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 使用者名稱 |
返回值:UserProfile?。
im.users.fetch()
批次查詢使用者資料,快取未過期的直接使用快取。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
usernames | List<String> | 是 | 使用者名稱,超過 100 個時分批查詢 |
force | bool | 否 | 命名引數,為 true 時不使用快取,預設 false |
返回值:Future<Map<String, UserProfile>>,鍵為小寫的使用者名稱;不存在或已刪除的使用者不在結果中。
可能的錯誤:not_signed_in。
im.friends.list()
返回全部好友的可訂閱列表(LiveList<Friend>),按備註、暱稱、使用者名稱中第一個非空的排序。快照的 hasMore 總是 false。不再使用時呼叫 dispose()。
未登入時丟擲 not_signed_in。
im.friends.get()
同步返回一位好友,不是好友時為 null。變化時發出 FriendsChanged。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 使用者名稱 |
返回值:Friend?。
im.friends.listInfo
只讀屬性:FriendListInfo?,好友數 count 和好友數上限 maxCount;好友列表還沒有同步時為 null。
im.friends.check()
檢查本人與其他使用者的關係。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
usernames | List<String> | 是 | 1 到 100 個使用者名稱 |
返回值:Future<List<FriendCheck>>,按請求中的順序;本人和不存在的使用者不在結果中。
可能的錯誤:local_validation(usernames 為空或超過 100 個)。
im.friends.update()
修改好友的備註和自定義屬性。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 好友的使用者名稱 |
remark | String? | 否 | 命名引數。備註,最長 64 個字元;空字串表示清空 |
attributes | Map<String, String?>? | 否 | 命名引數。自定義屬性,按項合併,值為 null 的鍵被刪除。最多 16 項,合計不超過 1 KB |
clearAttributes | bool | 否 | 命名引數。為 true 時清空全部自定義屬性,預設 false |
remark、attributes、clearAttributes 至少給出一項。
返回值:Future<Friend>。
可能的錯誤:local_validation(一項都沒有給出)、not_found(不是好友)、invalid_argument(備註或自定義屬性不符合規則)。
im.friends.remove()
刪除好友,雙方的好友列表都會移除對方。不是好友時同樣成功。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 好友的使用者名稱 |
返回值:Future<void>。
im.friends.requests.send()
傳送好友申請。不自動重發,規則見傳送申請。引數都是命名引數。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 對方的使用者名稱 |
message | String? | 否 | 附言,最長 256 個字元 |
remark | String? | 否 | 成為好友後給對方設定的備註,最長 64 個字元 |
addSource | String? | 否 | 新增來源,如 search、qrcode |
返回值:Future<FriendRequestResult>。
可能的錯誤:friend_add_denied、user_blocked、limit_exceeded、rate_limited、content_rejected、permission_denied、not_found(對方不存在)、timeout,見傳送申請。
im.friends.requests.received()
查詢收到的好友申請,按傳送時間從新到舊。引數都是命名引數。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
status | FriendRequestStatus? | 否 | 只列出這種狀態的申請:pending、accepted、declined、expired |
cursor | String? | 否 | 下一頁的遊標,取自上一頁的 nextCursor |
limit | int? | 否 | 每頁條數 |
返回值:Future<DRPage<FriendRequest>>。不帶 cursor、status 且 limit 不大於已快取的條數時返回 SDK 記憶體中的第一頁。
im.friends.requests.sent()
查詢本人發出的好友申請,引數和返回值同 im.friends.requests.received()。
im.friends.requests.unreadCount
只讀屬性:int,收到的未讀申請數,最多 100。變化時發出 FriendRequestsChanged。
im.friends.requests.markRead()
把收到的申請標為已讀。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
readUntil | DateTime? | 否 | 命名引數。已展示的最新一條申請的 requestedAt,這個時間及以前的申請標為已讀。省略時按 SDK 記憶體中第一頁的未讀申請計算 |
返回值:Future<void>。
im.friends.requests.accept()
同意好友申請,雙方成為好友。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 申請人的使用者名稱 |
remark | String? | 否 | 命名引數。給申請人設定的備註 |
返回值:Future<Friend>,新好友。
可能的錯誤:not_found(申請不存在或已過期)、limit_exceeded(好友數已滿)、permission_denied(被加好友前回撥拒絕)。
im.friends.requests.decline()
拒絕好友申請。對方 24 小時內不能再向本人申請。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 申請人的使用者名稱 |
返回值:Future<void>。
可能的錯誤:not_found(申請不存在、已同意或已過期)。
im.friends.blacklist.list()
返回黑名單的可訂閱列表(LiveList<BlacklistEntry>),按拉黑時間從新到舊。不再使用時呼叫 dispose()。
im.friends.blacklist.has()
同步判斷本人是否拉黑了某人。變化時發出 BlacklistChanged。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 使用者名稱 |
返回值:bool。
im.friends.blacklist.add()
把使用者加入黑名單。已在黑名單中時同樣成功。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 使用者名稱 |
返回值:Future<void>。
可能的錯誤:permission_denied(應用關閉了黑名單攔截,在本地拒絕)、limit_exceeded(黑名單已滿 1000 人,details['reason'] 為 blacklist_limit)、not_found(使用者不存在)、invalid_argument(拉黑自己)。
im.friends.blacklist.remove()
把使用者移出黑名單。不在黑名單中時同樣成功。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 使用者名稱 |
返回值:Future<void>。
im.friends.settings.get()
查詢本人的加好友方式。
返回值:Future<FriendAddSettings>。friendAddMode 為本人的設定,沒有設定過時為 null;effectiveFriendAddMode 為實際生效的方式。
im.friends.settings.set()
設定本人的加好友方式。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
mode | FriendAddMode? | 是 | needConfirm、allowAny、denyAny;null 清除設定,改用應用的預設值 |
返回值:Future<void>。
im.presence.subscribe()
登記要持續關注線上狀態的人。同步執行,不等待服務端的結果。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
usernames | List<String> | 是 | 使用者名稱 |
返回值:void Function(),取消這次訂閱的函式,可以重複呼叫。
可能的錯誤(同步丟擲):not_signed_in、permission_denied(presence_disabled)。客戶端已關閉時什麼也不做,返回的函式同樣什麼也不做。
im.presence.get()
同步返回已訂閱的人的線上狀態。變化時發出 PresenceChanged。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
username | String | 是 | 使用者名稱 |
返回值:PresenceState?,未知時為 null。
im.presence.query()
查詢一次線上狀態,不訂閱。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
usernames | List<String> | 是 | 1 到 100 個使用者名稱 |
返回值:Future<List<PresenceState>>;看不到的使用者不在結果中。
可能的錯誤:local_validation(為空或超過 100 個)、permission_denied(presence_disabled)、rate_limited。
im.devices.online()
同步返回本人當前線上的裝置。變化時發出 DevicesChanged。
返回值:List<OnlineDevice>。
im.devices.sessions()
查詢本人全部有效的登入裝置。
返回值:Future<List<LoginSession>>,按最近活躍時間從新到舊。
im.devices.kick()
讓本人的一臺裝置下線;指定的是當前裝置時等同於退出登入。裝置已經下線時同樣成功。
| 引數 | 型別 | 必填 | 說明 |
|---|---|---|---|
sessionId | String | 是 | 登入裝置的 sessionId |
返回值:Future<void>。
可能的錯誤:local_validation(sessionId 為空)、not_found(不是本人的登入裝置)。
相關事件
用 im.on<T>() 按型別訂閱,見事件與錯誤處理。
| 事件 | 欄位 | 說明 |
|---|---|---|
MeChanged | me(SelfProfile?) | 本人資料變化 |
MeMuteChanged | scope、muted、expiresAt | 本人的全域性禁言變化;scope 為 chat、group 或 room,expiresAt 為 null 表示永久 |
UsersChanged | usernames | 這些使用者(小寫)的快取資料有變化 |
FriendsChanged | upserted、removed | 好友列表變化(使用者名稱) |
BlacklistChanged | upserted、removed | 黑名單變化(使用者名稱) |
FriendRequestsChanged | lists、unreadCount | 好友申請或未讀數變化,lists 為 received、sent;SDK 記憶體中的第一頁已重新整理 |
PresenceChanged | items、reset、cleared | 線上狀態變化;reset 為 true 時全部狀態已清除,cleared 中的人的狀態已清除 |
PresenceSubscribeFailed | usernames、reason(PresenceFailure?) | 沒能訂閱;reason 為 denied、limitExceeded 或 presenceDisabled |
DevicesChanged | devices(List<OnlineDevice>) | 本人線上的裝置變化 |
DevicesNewLogin | sessionId、deviceName、platform、loginMethod、createdAt | 本人在新裝置上登入 |
資料結構
服務端物件(繼承 DRObject)的常用欄位以 getter 提供;時間為 UTC 的 DateTime。服務端返回的全部欄位都在 raw(Map<String, Object?>)中,可以直接讀取沒有 getter 的欄位。
SelfProfile
本人資料。
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 使用者名稱 |
nickname | String | 暱稱,沒有時為空字串 |
avatarUrl | String | 頭像地址,沒有時為空字串 |
attributes | Map<String, String>? | 自定義屬性,沒有時為 null |
hasPassword | bool | 是否設定過密碼 |
version | int | 資料的版本號,每次修改加一 |
UserProfile
其他使用者的公開資料。
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 使用者名稱 |
nickname | String | 暱稱,沒有時為空字串 |
avatarUrl | String | 頭像地址,沒有時為空字串 |
attributes | Map<String, String>? | 自定義屬性 |
displayName | String | 暱稱,沒有時為使用者名稱 |
MyMute 與 GlobalMuteInfo
| 欄位 | 型別 | 說明 |
|---|---|---|
MyMute.chat | GlobalMuteInfo? | 單聊的禁言,沒有禁言為 null |
MyMute.group | GlobalMuteInfo? | 群聊的禁言 |
MyMute.room | GlobalMuteInfo? | 聊天室的禁言 |
GlobalMuteInfo.expiresAt | DateTime? | 解除禁言的時間,永久禁言為 null |
Friend
本人視角的一位好友。
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 好友的使用者名稱 |
nickname | String | 好友的暱稱 |
avatarUrl | String | 好友的頭像地址 |
remark | String | 本人給好友設定的備註,沒有時為空字串 |
attributes | Map<String, String>? | 本人給好友設定的自定義屬性 |
addSource | String | 新增來源,沒有時為空字串 |
createdAt | DateTime? | 成為好友的時間 |
displayName | String | 備註、暱稱、使用者名稱中第一個非空的(好友列表按它排序) |
FriendListInfo
| 欄位 | 型別 | 說明 |
|---|---|---|
count | int | 好友數 |
maxCount | int | 好友數上限 |
FriendRequest
站在本人的角度描述一條好友申請:收到的申請中“對方”是申請人,發出的申請中“對方”是被申請人。
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 對方的使用者名稱 |
nickname | String | 對方的暱稱 |
avatarUrl | String | 對方的頭像地址 |
message | String | 附言,沒有時為空字串 |
addSource | String | 新增來源 |
status | FriendRequestStatus? | pending 待處理、accepted 已成為好友、declined 已拒絕、expired 已過期;不認識的狀態為 null(原值在 raw['status']) |
unread | bool | 只對收到的申請有意義:是否還沒有檢視過 |
remark | String? | 只在發出的申請中有:預先給對方設定的備註 |
requestedAt | DateTime? | 最近一次傳送的時間 |
expiresAt | DateTime? | 過期時間,只有 pending 的申請有值 |
handledAt | DateTime? | 同意、拒絕或成為好友的時間 |
FriendRequestResult
| 欄位 | 型別 | 說明 |
|---|---|---|
status | String | pending(等待對方處理)或 accepted(已成為好友) |
friend | Friend? | 直接成為好友時的好友資訊;超時後查詢確認已生效的沒有 |
accepted | bool | status 是否為 accepted |
FriendCheck
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 對方的使用者名稱 |
isFriend | bool | 是否好友 |
blocked | bool | 本人是否拉黑了對方 |
request | String? | 雙方之間未過期的待處理申請:sent 為本人發出,received 為對方發來;沒有為 null |
BlacklistEntry
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 被拉黑的使用者名稱 |
nickname | String | 暱稱 |
avatarUrl | String | 頭像地址 |
createdAt | DateTime? | 拉黑的時間 |
FriendAddSettings
| 欄位 | 型別 | 說明 |
|---|---|---|
friendAddMode | FriendAddMode? | 本人的設定,沒有設定過為 null |
effectiveFriendAddMode | FriendAddMode? | 實際生效的方式 |
FriendAddMode 與 FriendRequestStatus
FriendAddMode 為列舉 allowAny、needConfirm、denyAny,wire 為服務端的取值(allow_any 等),見加好友方式。FriendRequestStatus 為列舉 pending、accepted、declined、expired。
PresenceState
| 欄位 | 型別 | 說明 |
|---|---|---|
username | String | 使用者名稱 |
online | bool | 是否線上 |
platforms | List<String> | 線上的平臺,如 ios、android、web、windows;離線時為空列表 |
lastSeenAt | DateTime? | 最近一次離線的時間;線上、30 天內沒有上線過,或應用不允許檢視時為 null |
version | int | 狀態的版本號 |
PresenceFailure
列舉:denied(不存在或看不到)、limitExceeded(超出每臺裝置 3000 人,或服務端的訂閱總數已滿)、presenceDisabled(應用關閉了線上狀態)。
OnlineDevice
本人當前線上的一臺裝置。
| 欄位 | 型別 | 說明 |
|---|---|---|
sessionId | String | 登入裝置的會話 ID,與 LoginSession 的相同 |
platform | String | 平臺 |
connectedAt | DateTime? | 建立連線的時間 |
current | bool | 是否為當前這臺裝置 |
LoginSession
本人的一次有效登入。
| 欄位 | 型別 | 說明 |
|---|---|---|
sessionId | String | 會話 ID,用於 kick() |
deviceId | String? | 裝置標識 |
deviceName | String? | 裝置名稱 |
platform | String? | 平臺:ios、android、harmony、windows、macos、linux、web、mini_program 或 other |
current | bool | 是否為當前這臺裝置 |
raw 中另有 sdk_version(客戶端版本)、login_method(ticket 憑證登入、password 密碼登入)、ip(登入時的 IP)、created_at(登入時間)、last_active_at(最近一次登入或續期的時間)、expires_at(會話的過期時間,null 表示不過期),都是字串。
