Чат Flutter + Firestore .. Listview восстанавливает все элементы

Я пытаюсь добавить в свое приложение функции чата (что-то вроде функций WhatsApp), используя flutter и Firestore. Основная структура Firestore состоит из 2 коллекций (мне также нужен счетчик непрочитанных сообщений):

  1. users: и у каждого пользователя будет подколлекция «чаты», которая будет включать все CHATS_ID. Это будет основное место для создания домашней страницы чата (показывает список всех чатов), получая список чатов пользователя.
  2. чаты: список всех чатов, и каждый документ чата имеет подколлекцию сообщений.

Моя основная проблема заключается в создании домашней страницы (где должен появиться список всех предыдущих чатов пользователей). Я получаю / подписываюсь на подколлекцию пользовательского чата, и для каждого указанного там идентификатора чата я также подписываюсь на сам чат в коллекции чата (используя идентификатор).

Вот его принципиальные скриншоты:

Коллекция пользователей:

сборник чатов:

и вот главный интересующий экран (принцип из экрана 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 предполагала, что, поскольку поток можно изменить (github: [https://github.com/flutter/flutter/issues/58917]), но даже при использовании ListView.custom с делегатом и findChildIndexCallback проблема осталась.
  2. использовать отличное.

Но удаление внутреннего streambuilder и просто возврат тайлов без подписки заставляет ListView.builder работать должным образом (строит только просматриваемые). Итак, мои вопросы:

  1. Зачем нужны вложенные построители потоков, заставляющие перестраивать все элементы.
  2. есть ли лучшая структура для реализации вышеуказанных функций (все чаты с непрочитанным счетчиком и последним сообщением / временем в реальном времени). Тем более что ленивую загрузку я еще не добавил. Кроме того, с этим дизайном я должен обновлять несколько документов для каждого сообщения (в коллекции чатов и подколлекции каждого пользователя).

Мы будем очень благодарны за вашу помощь (я проверил некоторые другие темы SO и статьи среднего уровня, но не смог найти ни одной, которая сочетала бы эти функции в одном месте и, желательно, с оптимизированным дизайном для масштабируемости / цены с использованием Firestore и Flutter).

1 ответ

Думаю, что у Вас получится:

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

и для MsgWrapper:

        class MsgWrapper extends StatelessWidget {
    const MsgWrapper(this.index);
    int index;
    Widget build(ctx) {
      return StreamBuilder(
        stream: FirebaseFirestore.instance
          .collection(AppConstants.CHATS_COLLECTION)
          .doc('$chatId')
          .snapshots()
          .distinct(),
        builder: (snapshot, ctx) => Text('Message'),// with the builder 
                                  //returning the widget that
                                  //represents a message
      );
    }
  }
Другие вопросы по тегам