我正在尝试使用颤振和 Firestore 向我的应用程序添加聊天功能(类似于 WhatsApp 功能)。Firestore 的主要结构是有 2 个集合(我也想要未读消息计数):
- users:每个用户都有一个包含所有 CHATS_ID 的子集合“chats”。这将是通过获取用户聊天列表来构建主页聊天页面(显示所有聊天的历史列表)的主要位置。
- 聊天:所有聊天的列表,每个聊天文档都有一个消息子集合。
我的主要问题是构建主页(应显示所有用户以前聊天的列表)。我获取/订阅用户聊天子集合,并且对于其中列出的每个聊天 ID,我还订阅聊天集合中的聊天本身(使用 ID)。
以下是它的原则截图:
用户集合:
聊天收藏:
我正在做的是检索用户的聊天子集合(并使用 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 一起使用,同样的问题仍然存在。
- 使用不同的。
但是删除内部流构建器并仅在没有订阅的情况下返回图块,使 ListView.builder 按预期工作(仅构建查看的图块)。所以我的问题是:
- 为什么有嵌套的流构建器会导致所有项目都被重新构建。
- 是否有更好的结构来实现上述功能(所有聊天都具有未读计数和实时最后一条消息/时间)。特别是我还没有添加延迟加载。而且使用这种设计,我必须为每条消息更新多个文档(在聊天集合和每个用户的子集合中)。
您的帮助将不胜感激(我已经检查了一些其他 SO 线程和中型文章,但找不到将这些功能结合在一个地方的文章,最好是使用 Firestore 和 Flutter 为可扩展性/价格优化设计)。