Flutter中initState内StreamSubscription第二次进入页面才返回数据问题
问题原因
核心问题出在以下几个逻辑漏洞:
- 你将
StreamZip转为了广播流,广播流会丢弃订阅之前发出的事件,首次进入页面时如果网络请求返回速度快,会直接丢失用户数据事件,导致chatUsersList没有拿到有效数据 - 流回调中你采用追加数据的方式更新列表,而非全量覆盖,会出现重复数据;如果
chatUsersList是全局/静态变量,上一次进入页面的残留数据或者其他页面写入的数据会导致列表提前满足非空条件,在需要的用户数据还没加载完成时就渲染正式页面,此时获取不到对应头像就会返回默认图 - 仅依赖
chatUsersList的长度作为渲染判断条件不可靠,没有增加加载状态控制,异步请求未完成时逻辑容易出现混乱
修复方案
按以下步骤修改代码即可解决问题:
- 进入页面时先清空用户列表,避免残留数据干扰
- 移除不必要的
asBroadcastStream调用,避免事件丢失 - 流回调中采用全量覆盖的方式更新用户列表,避免重复数据
- 增加加载状态作为渲染判断的前置条件,异步数据加载完成后再关闭加载状态
修改后的核心代码
initState部分
List chatUsersList = []; StreamSubscription? chatUsers; bool isLoading = true; // 初始状态为加载中 @override void initState() { super.initState(); // 进入页面先清空列表,清除残留数据 chatUsersList.clear(); FirebaseFirestore.instance.collection('chatUsers').get().then((snap) { var _chunks = []; List<Stream<dynamic>> _streams = []; final documents = snap.docs.map((doc) => UserModel.fromFirestore(doc)).toList(); // 兼容没有聊天用户的特殊情况 if (documents.isEmpty) { setState(() { isLoading = false; }); return; } final count = 10; for (var i = 0; i < documents.length; i += count) { _chunks.add( documents.sublist( i, i + count > documents.length ? documents.length : i + count, ), ); } final uids = _chunks.map((x) => x.map((y) => y.id).toList()).toList(); for (var i = 0; i < uids.length; i++) { Stream stream = FirebaseFirestore.instance .collection('users') .where('uid', whereIn: uids[i]) .snapshots(); _streams.add(stream); } StreamZip myStreams = StreamZip(_streams); // 移除asBroadcastStream,直接监听普通流即可 chatUsers = myStreams.listen((value) { final d = value.toList(); List tempList = []; for (var i = 0; i < d.length; i++) { var users = d[i].docs.map((u) => UserModel.fromFirestore(u)).toList(); tempList.addAll(users); } setState(() { // 全量覆盖列表,不要追加 chatUsersList = tempList; // 数据加载完成,关闭加载状态 isLoading = false; }); }); }); focusNode.addListener(onFocusChange); listScrollController.addListener(_scrollListener); isShowSticker = false; imageUrl = ''; readLocal(); } @override void dispose() { chatUsers?.cancel(); super.dispose(); }
build渲染逻辑部分
Widget build(BuildContext context) { // 优先判断加载状态,加载中展示加载动画 if (isLoading) { return const Center(child: CircularProgressIndicator()); } return chatUsersList.isNotEmpty ? WillPopScope(...) : Container(); }
额外优化建议
建议将chatUsersList改为当前State类的内部实例变量,不要定义为全局/静态变量,避免其他页面的修改影响当前页面的逻辑。
内容的提问来源于stack exchange,提问作者William
相关产品推荐
相关产品推荐

