Flutter 快速接入
精确安装 WuKongEasySDK Flutter 1.1.0,用可连续复制的 Dart 代码完成连接、在线收发与 Widget 清理。
使用一个 StatefulWidget 依次完成安装、初始化、监听、单聊发送和 dispose 清理。Alice 与 Bob 运行在两个独立设备、模拟器或浏览器上下文中。
当前 Product Gateway 支持这条连接路径
当前 Product Gateway 支持固定 v1.1.0 的 JSON-RPC CONNECT 与在线双向收发:客户端以 Base64 发送,服务端对有效 JSON 输出对象 RECV。设备线路值为 APP 0、WEB 1、PC 2。教程使用 pub.dev 1.1.0 与对应 Release;同一 revision 98ab8f3d9a1ad53f40c32caef0979845a37ae9a6 的官方 example 与从 pub.dev 解析的正式包均已在 iOS Simulator 完成双向消息和断开。其他 Flutter 目标、WSS 与生产 Token 校验仍须独立验收。
完成后你会得到什么
pubspec.yaml中锁定为1.1.0的依赖;- 一个不会在 Widget 重建时重复注册监听器的页面;
- Alice 的发送结果与 Bob 的实时消息事件;
- 清晰的退出、去重、Token、WSS 与后续离线能力边界。
先运行官方 example(推荐)
git clone https://github.com/WuKongIM/WuKongEasySDK-Flutter.git
cd WuKongEasySDK-Flutter
git checkout 98ab8f3d9a1ad53f40c32caef0979845a37ae9a6
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter run -d <device-id>这个 revision 就是正式 v1.1.0;源码 example 和 hosted 正式包的完整服务端准备、设备地址及 Alice/Bob 验收见运行官方示例。
开始前
准备以下条件:
- Flutter 3.0 或更高版本;
- Dart 3.0 或更高版本;
- 一个
/readyz正常、目标设备可访问 WebSocket Gateway 的 WuKongIM 单节点集群或多节点集群; - 业务后端能为 Alice 和 Bob 分别返回
uid、短期token与websocketUrl。
先阅读身份与 Token。如果应用同时构建移动端、桌面端和 Web,请把每个目标分别验收,不能用一个平台的结果推断全部平台。
步骤 1:安装精确版本
在 pubspec.yaml 中添加精确版本:
dependencies:
flutter:
sdk: flutter
wukong_easy_sdk: 1.1.0然后执行:
flutter pub get提交更新后的 pubspec.lock,确保 CI 与开发机安装同一版本。
步骤 2:接收业务后端的连接材料
class IMBootstrap {
const IMBootstrap({
required this.uid,
required this.token,
required this.websocketUrl,
});
final String uid;
final String token;
final String websocketUrl;
}业务后端先验证产品登录,再返回这三个字段。客户端不持有 Product HTTP 管理凭据,生产地址使用 wss://。
步骤 3:初始化并保存监听器引用
以下页面只在 initState 触发一次初始化。每个回调都保存为字段,才能在 dispose 中传回同一个函数引用。
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
class ChatPage extends StatefulWidget {
const ChatPage({required this.bootstrap, super.key});
final IMBootstrap bootstrap;
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final easySDK = WuKongEasySDK.getInstance();
final messagesById = <String, Message>{};
late final WuKongEventListener<ConnectResult> connectListener;
late final WuKongEventListener<DisconnectInfo> disconnectListener;
late final WuKongEventListener<Message> messageListener;
late final WuKongEventListener<WuKongError> errorListener;
bool listenersRegistered = false;
bool connected = false;
@override
void initState() {
super.initState();
_createListeners();
_start();
}
void _createListeners() {
connectListener = (result) {
if (!mounted) return;
setState(() => connected = true);
debugPrint('connected');
};
disconnectListener = (info) {
if (!mounted) return;
setState(() => connected = false);
debugPrint('disconnected');
};
messageListener = (message) {
if (!mounted) return;
setState(() => messagesById[message.messageId] = message);
};
errorListener = (error) {
debugPrint('EasySDK operation failed');
};
}
Future<void> _start() async {
final config = WuKongConfig(
serverUrl: widget.bootstrap.websocketUrl,
uid: widget.bootstrap.uid,
token: widget.bootstrap.token,
debugLogging: false,
);
try {
await easySDK.init(config);
if (!mounted) {
easySDK.dispose();
return;
}
_registerListeners();
await easySDK.connect().timeout(
const Duration(seconds: 20),
onTimeout: () {
easySDK.disconnect();
throw TimeoutException('EasySDK connect exceeded 20 seconds');
},
);
} catch (_) {
easySDK.disconnect();
debugPrint('EasySDK connect failed');
}
}
void _registerListeners() {
if (listenersRegistered) return;
easySDK.addEventListener(WuKongEvent.connect, connectListener);
easySDK.addEventListener(WuKongEvent.disconnect, disconnectListener);
easySDK.addEventListener(WuKongEvent.message, messageListener);
easySDK.addEventListener(WuKongEvent.error, errorListener);
listenersRegistered = true;
}
@override
void dispose() {
if (listenersRegistered) {
easySDK.removeEventListener(WuKongEvent.connect, connectListener);
easySDK.removeEventListener(WuKongEvent.disconnect, disconnectListener);
easySDK.removeEventListener(WuKongEvent.message, messageListener);
easySDK.removeEventListener(WuKongEvent.error, errorListener);
}
easySDK.disconnect();
easySDK.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(connected ? 'Online' : 'Connecting')),
body: ListView(
children: messagesById.values
.map((message) => ListTile(title: Text(_displayPayload(message.payload))))
.toList(),
),
);
}
String _displayPayload(dynamic payload) {
if (payload is String) {
try {
return utf8.decode(base64Decode(payload));
} catch (_) {
return payload; // 保留未知或非 Base64 Payload,交给降级 UI。
}
}
return jsonEncode(payload);
}
}debugLogging: false 与 SDK 默认值相同,这里显式写出以便审查生产配置。只有在受控诊断窗口中才设置为 true;若同时传入 logHandler,它只会收到 SDK 已脱敏的运行元数据,应用仍不得把完整事件、模型或 Payload 追加进去。
页面级 dispose() 适合最小示例。Future.timeout 把完整连接等待限制为 20 秒,并在超时或其他失败后断开;不要让页面永久停在 Connecting。真实应用如果希望切换页面时连接保持,应让 Provider、Riverpod、Bloc 或其他应用级状态容器拥有 SDK;页面只添加和移除自己的监听器,最终退出账号时再 disconnect 与 dispose。
步骤 4:发送第一条消息
在 _ChatPageState 中加入:
Future<void> sendText(String targetUid, String text) async {
if (!connected) throw StateError('EasySDK is not connected');
await easySDK.send(
channelId: targetUid,
channelType: WuKongChannelType.person,
payload: {
'type': 1,
'version': 1,
'content': text,
},
);
debugPrint('SEND completed');
}调用 sendText('bob', 'Hello from Flutter EasySDK') 后,Alice 记录 SendResult。Bob 必须在自己的 messageListener 中独立看到消息;发送结果和实时接收不是同一个事件。
步骤 5:用 Alice 和 Bob 验收
- 在两台设备、两个模拟器或两个独立浏览器上下文中分别启动 Alice 与 Bob;
- 两端都等到
WuKongEvent.connect后再启用发送; - Alice 向个人 Channel
bob发送消息,记录 ID、序号和 Reason Code; - Bob 核对
fromUid、channelId,检查对象 Payload 的type与content,并按messageId去重; - Bob 向 Alice 回发,验证反向链路;
- 销毁并重新打开页面,确认没有重复监听,再验证退出账号后的资源释放。
上述正式 v1.1.0 example 已在 iOS Simulator 连接同一版 WuKongIM,完成双向消息;25 个测试和静态分析也通过。从 pub.dev 解析且 package config 标记为 hosted 的同版本产物随后在托管 iOS Simulator 再次完成双向消息和断开。该闭环没有证明 Android、Web、桌面目标、应用后台恢复、离线消息同步或多端会话一致性,继续按消息收发和上线检查补齐。
常见问题
- 初始化或切换 UID 后状态异常:等待
easySDK.init(config)完成后再连接;切换账号前先移除监听、断开并dispose,同时清空产品本地状态。 - Widget 重建后重复收消息:不要在
build中注册监听器;保存回调引用并在dispose移除。 - 系统日志仍出现 Token 或 Payload:先确认锁文件实际解析到
wukong_easy_sdk 1.1.0,并检查应用回调、debugPrint、自定义logHandler与采集器是否记录了完整事件或模型;SDK 诊断默认关闭,显式开启时也只应输出脱敏运行元数据。用 Release 产物复现并保留低敏证据后再提交问题。 message.payload是一段字符串:当前服务端对有效 JSON 对象输出对象;若仍收到字符串,先确认服务端 revision,再尝试按 Base64 → UTF-8 JSON 解码,未知格式保留原值并降级展示。- 真机连不上本地地址:
localhost指向真机自身,让业务后端返回设备可达的 WSS 地址。 - 发送成功但 UI 没有对方消息:把发送结果、实时接收和离线恢复分开检查。
上线前检查
- 为 Android、iOS、Web、桌面等每个实际目标分别保存构建与运行结果,不跨平台推断;
- 使用
wss://,验证证书、代理 Upgrade、连接超时、前后台切换与断网恢复; debugLogging默认关闭;生产保持false,若使用logHandler,只能接收 SDK 已脱敏元数据,应用不得追加 Token、Payload 或完整模型;- 按 APP
0、WEB1、PC2核对设备值,保存pubspec.lock、Release 产物、设备、网络和服务端 revision; - 使用上线验收继续验证离线、推送、多设备、容量、升级与回滚。
下一步
回到 WuKongEasySDK 概览,或继续阅读消息收发、会话与未读数和上线检查。