【问题标题】:Flutter + Firestore chat .. Listview rebuilds all itemsFlutter + Firestore 聊天.. Listview 重建所有项目
【发布时间】:2021-08-25 09:14:45
【问题描述】:

我正在尝试使用 Flutter 和 Firestore 将聊天功能添加到我的应用程序(类似于 WhatsApp 功能)。 Firestore 的主要结构是有 2 个集合(我也想要未读消息计数):

  1. users:每个用户都有一个包含所有 CHATS_ID 的子集合“chats”。这将是通过获取用户聊天列表来构建主聊天页面(显示所有聊天的历史列表)的主要位置。
  2. 聊天:所有聊天的列表,每个聊天文档都有一个消息子集合。

我的主要问题是构建主页(应显示所有用户以前聊天的列表)。我获取/订阅用户聊天子集合,并且对于其中列出的每个聊天 ID,我还在聊天集合中订阅聊天本身(使用 ID)。

原理图如下:

用户集合:

聊天收藏:

这是感兴趣的主屏幕(原理来自 whatsapp 屏幕):

我正在做的是检索用户的聊天子集合(并使用 StreamBuilder 为其注册一个侦听器),以及未读消息/最后一条消息和最后一条消息时间的数量,我订阅以收听这些聊天中的每一个(并希望使用每个用户的最后一条消息时间、状态和他在该聊天文档中的最后一次出现来计算未读计数)。

问题在于 Listview.builder 重建所有项目(最初和滚动时),而不仅仅是查看的项目。这是我的代码:

  Stream<QuerySnapshot> getCurrentUserChats(userId) {
    return FirebaseFirestore.instance
        .collection(AppConstants.USERS_COLLECTION)
        .doc('$userId')
        .collection(AppConstants.USER_CHATS_SUBCOLLECTION)
        .orderBy('lastMsgTS', descending: true)
        .snapshots()
        .distinct();
  }

  Widget getRecentChats(userId) {
    return StreamBuilder<QuerySnapshot>(
        stream: getCurrentUserChats(userId),
        builder: (context, snapshot) {
          if (snapshot.hasData && snapshot.data.docs.isNotEmpty) {
            print('snapshot of user chats subcoll has changed');
            List<QueryDocumentSnapshot> retrievedDocs = snapshot.data.docs;
            return Container(
              height: 400,
              child: ListView.builder(
                //childrenDelegate: SliverChildBuilderDelegate(
                itemCount: snapshot.data.size,
                itemBuilder: (context, index) {
                  String chatId = retrievedDocs[index].id;
                  print('building index: $index, chatId: $chatId');

                  return StreamBuilder(
                    stream: FirebaseFirestore.instance
                        .collection(AppConstants.CHATS_COLLECTION)
                        .doc('$chatId')
                        .snapshots()
                        .distinct(),
                    builder:
                        (context, AsyncSnapshot<DocumentSnapshot> snapshot) {

                      if (snapshot.hasData) {
                        print('${snapshot.data?.id}, isExist: ${snapshot.data?.exists}');
                        if (snapshot.data.exists) {
                          return KeyProxy(
                            key: ValueKey(chatId),
                            child: ListTile(
                              leading: CircleAvatar(
                                child: Container(
                                  //to be replaced with user image
                                  color: Colors.red,
                                ),
                              ),
                              title: Text('$chatId'),
                              subtitle: Text(
                                  "Last Message received on: ${DateTimeUtils.getDateViewFromDT(snapshot.data.data()['ts']?.toDate())}"),
                            ),
                          );
                        }
                      }

                      return SizedBox.shrink();
                    },
                  );
                },
                /*childCount: snapshot.data.size,
                      findChildIndexCallback: (Key key) {
                        print('calling findChildIndexCallback');
                        final ValueKey valKey = key;
                        final String docId = valKey.value;
                        int idx = retrievedDocs.indexOf(retrievedDocs
                            .where((element) => element.id == docId)
                            .toList()[0]);
                        print('docId: $docId, idx: $idx');
                        return idx;
                      }*/
              ),
            );
          }

          return Center(child: UIWidgetUtils.loader());
        });
  }

经过搜索,我找到了这些相关的建议(但都不起作用):

  1. 由于流是可重新排序的(github:[https://github.com/flutter/flutter/issues/58917]),因此建议 github 问题,但即使将 ListView.custom 与委托和 findChildIndexCallback 一起使用,也是如此问题依然存在。
  2. 使用 distinct。

但删除内部流构建器并仅返回没有订阅的图块,会使 ListView.builder 按预期工作(仅构建查看的图块)。所以我的问题是:

  1. 为什么嵌套流构建器会导致所有项目都被重新构建。
  2. 是否有更好的结构来实现上述功能(所有聊天都具有未读计数和实时最后一条消息/时间)。特别是我还没有添加延迟加载。而且在这种设计中,我必须为每条消息更新多个文档(在聊天集合和每个用户的子集合中)。

您的帮助将不胜感激(我查看了其他一些 SO 线程和中型文章,但找不到将这些功能结合在一个地方的文章,最好是使用 Firestore 和 Flutter 为可扩展性/价格优化设计)。

【问题讨论】:

    标签: flutter google-cloud-firestore chat flutter-listview flutter-streambuilder


    【解决方案1】:

    我认为你可以这样做:

      Widget build(ctx) {
        return ListView.builder(
          itemCount: snapshot.data.size,
          itemBuilder: (index, ctx) =>_catche[index],
        )
      }
    

    对于 _catch:

      List<Widget> _catche = [/*...*/];
      // initialize on load
    

    【讨论】:

    • 感谢 Om Patil,但 const 无法处理以下错误“创建常量的参数必须是常量表达式。”所以我无法传递索引或其他参数。
    猜你喜欢
    • 2021-08-08
    • 2020-05-14
    • 1970-01-01
    • 2021-08-18
    • 1970-01-01
    • 1970-01-01
    • 2017-04-19
    • 2021-06-21
    • 2021-10-02
    相关资源
    最近更新 更多