Flutter:如何无需无限循环Stream即可用StreamBuilder监听数据变化
替代Timer无限循环的Flutter Stream数据监听方案
现有问题分析
你当前通过Timer.periodic(Duration(seconds: 0))实现无限循环请求数据,这种方式会导致前端疯狂向后端发送请求,不仅浪费资源,还会严重影响性能。下面提供两种更合理的实现方式:
方案一:用Stream.periodic实现可控轮询
直接通过Stream.periodic创建定时轮询的数据流,替代手动维护StreamController和Timer的组合,代码更简洁且易于维护:
1. 定义轮询Stream
Stream<UserModel> getFriendsStream() { // 设置合理的轮询间隔,比如5秒,避免频繁请求 return Stream.periodic(const Duration(seconds: 5), (_) async { // 提前获取SharedPreferences,避免每次请求重复初始化 SharedPreferences prefs = await SharedPreferences.getInstance(); var token = prefs.getString("token"); var username = prefs.getString("username"); try { final response = await http.post( Uri.parse("http://192.168.0.111:3000/friendNames"), headers: { "Content-Type": "application/json", "authorization": "$token" }, body: jsonEncode({"username": username}), ); return UserModel.fromJson(json.decode(response.body)); } catch (e) { print("请求好友列表失败: $e"); // 抛出错误,让StreamBuilder可以捕获并处理 rethrow; } }).asyncMap((future) => future); }
2. 初始化Stream并使用StreamBuilder
late Stream<UserModel> _friendsStream; @override void initState() { super.initState(); _friendsStream = getFriendsStream(); } // StreamBuilder部分修改为使用_friendsStream StreamBuilder<UserModel>( stream: _friendsStream, builder: (context, snapshot) { // 新增错误状态处理 if (snapshot.hasError) { return const Center(child: Text("加载好友列表失败")); } if (snapshot.hasData) { final userModel = snapshot.data!; if (userModel.msg == "no friends to chat with") { return const Center(child: Text("No friends found.")); } else { return ListView.builder( itemCount: userModel.msg.length, itemBuilder: (context, index) { return ListTile( title: Text(userModel.msg[index]), subtitle: Text("${userModel.msg1![index]}"), leading: CircleAvatar( backgroundColor: Colors.orange, backgroundImage: NetworkImage( "http://192.168.0.111:3000/getImage/${userModel.msg[index]}?v=${Random().nextInt(100)}", ), ), onTap: () => Navigator.of(context).push( MaterialPageRoute(builder: (context) => ChatRoom(userModel.msg[index])), ), ); }, ); } } else { return const Center(child: CircularProgressIndicator()); } }, )
方案二:使用WebSocket实现实时推送(推荐)
如果后端支持WebSocket,可以实现真正的实时监听,后端在好友列表变化时主动推送数据,彻底告别轮询:
1. 初始化WebSocket连接
late WebSocket _webSocket; late StreamController<UserModel> _friendsController; @override void initState() { super.initState(); _friendsController = StreamController.broadcast(); _connectWebSocket(); } void _connectWebSocket() async { SharedPreferences prefs = await SharedPreferences.getInstance(); var token = prefs.getString("token"); // 连接后端WebSocket服务,需后端配合实现 try { _webSocket = await WebSocket.connect( "ws://192.168.0.111:3000/friends-ws?token=$token", ); // 监听后端推送的消息 _webSocket.listen((message) { final userModel = UserModel.fromJson(json.decode(message)); _friendsController.sink.add(userModel); }, onError: (error) { print("WebSocket连接错误: $error"); // 可选:添加重连逻辑 }, onDone: () { print("WebSocket连接关闭"); // 可选:添加重连逻辑 }); } catch (e) { print("WebSocket连接失败: $e"); } } // 记得在页面销毁时释放资源 @override void dispose() { _webSocket.close(); _friendsController.close(); super.dispose(); }
2. StreamBuilder使用WebSocket数据流
StreamBuilder<UserModel>( stream: _friendsController.stream, builder: (context, snapshot) { // 复用之前的builder逻辑,包含错误、空数据、正常数据的处理 // ... }, )
额外优化建议
- 避免在每次请求中重复获取
SharedPreferences,可以在initState中提前获取并保存token和username - 处理网络请求的超时情况,避免请求长时间阻塞
- 对于NetworkImage的随机参数,可以考虑用时间戳代替Random,避免不必要的重复加载
内容的提问来源于stack exchange,提问作者alaa
相关产品推荐
相关产品推荐

