You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Firestore未读消息数Stream数据先显后空问题排查

问题:Firestore聊天应用未读消息数量初始显示后立即变为null

在开发基于Firebase Firestore的聊天应用时,尝试在聊天列表中展示未读消息数量,初始能正确显示数字,但随后数据立刻变为null。

相关代码

聊天列表StreamBuilder代码

body: StreamBuilder(
    stream: FirebaseFirestore.instance
        .collection('chatRooms')
        .where('emails', arrayContainsAny: [user?.email]).snapshots(),
    builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
      var chatLists = snapshot.data?.docs;

      if (snapshot.hasError) {
        return Text('Something error...');
      }

      if (snapshot.connectionState == ConnectionState.waiting) {
        return Text('is Loading...');
      }
      return ListView.builder(
        itemCount: chatLists?.length,
        itemBuilder: (context, index) {
          if (chatLists?[index]['currentMsg'] != null &&
              chatLists?[index]['currentMsg'] != "") {
            var list = List.from(chatLists?[index]['members']);
            var member = '';

            if (loginUser['userName'] != null) {
              for (int i = 0; i < list.length; i++) {
                if (list[i] != loginUser['userName']) {
                  member += list[i];
                }
              }
            }

            return ListTile(
              title: Row(
                children: [
                  Text(member),
                  const SizedBox(
                    width: 20.0,
                  ),
                  ChatLength(docId: chatLists![index].id, uid: user!.uid),
                ],
              ),
              subtitle: SizedBox(
                  height: 40.0,
                  child: Text(
                    chatLists[index]['currentMsg'],
                    overflow: TextOverflow.ellipsis,
                  )),
              trailing: Text(
                tmFormmater(chatLists?[index]['currentTm']),
                style: const TextStyle(
                  color: Color(0xff999999),
                ),
              ),
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => ChatScreen(
                      docId: chatLists![index].id,
                      title: member,
                    ),
                  )),
            );
          } else {
            return Container();
          }
        },
      );

      return Container();
    },
  ),

ChatLength组件代码

class ChatLength extends StatelessWidget {
  const ChatLength({super.key, required this.docId, required this.uid});

  final String docId;
  final String uid;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: FirebaseFirestore.instance
          .collection('message_read')
          .doc(docId)
          .collection('message')
          .where('userId', isNotEqualTo: uid)
          .where('read', isEqualTo: false)
          .snapshots(),
      builder: (context, snapshot) {
        print(snapshot.data?.size);
        if (snapshot.data?.size != null) {
          return Container(
            child: Text('${snapshot.data?.size}'),
          );
        } else {
          return Container();
        }
      },
    );
  }
}

调试日志

===== Debug console =====
flutter: 1 
flutter: null    //  Data changes to null immediately

Firestore集合结构

message_read -> documentId -> message -> documentId -> field(userId(chat writer), read)


问题原因

ChatLength组件中的StreamBuilder没有正确处理快照的状态:

  1. 当Firestore的stream首次返回数据时,snapshot.data?.size能获取到有效值;
  2. 当stream触发更新(比如集合数据变化、连接状态波动)时,会先返回一个snapshot.data为null的中间状态,此时直接判断snapshot.data?.size != null会返回false,导致显示空Container;
  3. 没有处理连接等待状态和错误状态,加剧了数据显示的不稳定。

解决方案

修改ChatLength组件的StreamBuilder,完善状态判断逻辑,确保只在数据有效时显示未读数量:

class ChatLength extends StatelessWidget {
  const ChatLength({super.key, required this.docId, required this.uid});

  final String docId;
  final String uid;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance
          .collection('message_read')
          .doc(docId)
          .collection('message')
          .where('userId', isNotEqualTo: uid)
          .where('read', isEqualTo: false)
          .snapshots(),
      builder: (context, snapshot) {
        // 处理错误状态
        if (snapshot.hasError) {
          return const SizedBox();
        }

        // 处理加载状态
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const SizedBox();
        }

        // 确保数据存在再获取数量
        final unreadCount = snapshot.data?.size ?? 0;
        print(unreadCount);
        
        // 只有未读数量大于0时才显示
        return unreadCount > 0 
            ? Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                decoration: BoxDecoration(
                  color: Colors.red,
                  borderRadius: BorderRadius.circular(10),
                ),
                child: Text(
                  '$unreadCount',
                  style: const TextStyle(color: Colors.white, fontSize: 12),
                ),
              )
            : const SizedBox();
      },
    );
  }
}

优化点说明

  • 明确指定StreamBuilder的泛型为QuerySnapshot,避免类型模糊;
  • 增加错误状态判断,出错时返回空容器;
  • 增加加载状态判断,等待时返回空容器;
  • 使用??运算符给未读数量设置默认值0,避免null;
  • 仅当未读数量大于0时才显示数字,符合聊天应用的常规交互;
  • 给数字容器添加样式,提升视觉效果。

内容的提问来源于stack exchange,提问作者user20455755

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 04:20:14