Чат Flutter + Firestore .. Listview восстанавливает все элементы
Я пытаюсь добавить в свое приложение функции чата (что-то вроде функций WhatsApp), используя flutter и Firestore. Основная структура Firestore состоит из 2 коллекций (мне также нужен счетчик непрочитанных сообщений):
- users: и у каждого пользователя будет подколлекция «чаты», которая будет включать все CHATS_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());
});
}
После поиска я нашел эти похожие предложения (но оба не сработали):
- Проблема с github предполагала, что, поскольку поток можно изменить (github: [https://github.com/flutter/flutter/issues/58917]), но даже при использовании ListView.custom с делегатом и findChildIndexCallback проблема осталась.
- использовать отличное.
Но удаление внутреннего streambuilder и просто возврат тайлов без подписки заставляет ListView.builder работать должным образом (строит только просматриваемые). Итак, мои вопросы:
- Зачем нужны вложенные построители потоков, заставляющие перестраивать все элементы.
- есть ли лучшая структура для реализации вышеуказанных функций (все чаты с непрочитанным счетчиком и последним сообщением / временем в реальном времени). Тем более что ленивую загрузку я еще не добавил. Кроме того, с этим дизайном я должен обновлять несколько документов для каждого сообщения (в коллекции чатов и подколлекции каждого пользователя).
Мы будем очень благодарны за вашу помощь (я проверил некоторые другие темы 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
);
}
}