用户、好友与在线状态
本页介绍与“人”有关的功能:本人的资料、其他用户的资料、好友和好友申请、黑名单、在线状态,以及本人的登录设备。
- 用户资料(
im.users):本人资料随登录取得并保存在本地,可以修改昵称、头像和自定义属性;其他用户的资料按需批量获取并缓存在本地数据库中,列表界面直接同步读取。 - 好友(
im.friends):好友列表和黑名单完整保存在本地,以可订阅的列表(LiveList)提供,其他设备上的变化实时同步过来;好友申请按页查询。 - 在线状态(
im.presence):订阅要显示的人,状态变化时收到事件;也可以查询一次。 - 登录设备(
im.devices):查看本人当前在线的设备和全部登录设备,踢掉其他设备。
这些方法都要在登录之后调用(见初始化、登录与连接)。可订阅的列表、同步读取的状态和事件的通用规则见事件与错误处理,在 Widget 中使用列表见在 Flutter 界面中使用。
本人资料
读取本人资料
im.users.me() 同步返回本地保存的本人资料,未登录时为 null。资料在其他设备上被修改、或被你的服务端修改时,SDK 自动更新本地的资料并发出 MeChanged 事件。
import 'package:deeprespond_im/deeprespond_im.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/deeprespond_im.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/render.dart 中的 displayName(im, username),它同样同步读取,资料到达后随事件更新:
import 'package:deeprespond_im/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/deeprespond_im.dart';
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/deeprespond_im.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 表示不过期),都是字符串。
