Flutter+Firestore实现无SharedPreferences的最近聊天联系人列表
核心思路
基于Firebase Firestore存储最近聊天记录,通过监听用户专属的recent_chats子集合实现实时更新,同时保留搜索功能。
步骤1:调整Firebase数据结构
在每个用户的文档下添加recent_chats子集合,每个文档对应一个最近聊天会话,字段示例:
partnerUserId: 对方用户ID(String)lastMessage: 最后一条消息内容(String)timestamp: 消息发送时间戳(Timestamp)unreadCount: 未读消息数(int,可选)partnerUserInfo: 对方用户的昵称、头像等基础信息(Map,避免重复查询users集合)
步骤2:首页实现最近聊天列表 + 搜索功能
替换原首页仅展示搜索用户的逻辑,改用StreamBuilder监听recent_chats集合,同时叠加搜索过滤:
class ChatHomePage extends StatefulWidget { final String currentUserId; const ChatHomePage({super.key, required this.currentUserId}); @override State<ChatHomePage> createState() => _ChatHomePageState(); } class _ChatHomePageState extends State<ChatHomePage> { final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('聊天'), bottom: PreferredSize( preferredSize: const Size.fromHeight(56), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: TextField( controller: _searchController, onChanged: (value) { setState(() { _searchQuery = value.toLowerCase(); }); }, decoration: const InputDecoration( hintText: '搜索联系人或聊天', prefixIcon: Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(24)), ), ), ), ), ), ), body: StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance .collection('users') .doc(widget.currentUserId) .collection('recent_chats') .orderBy('timestamp', descending: true) .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return const Center(child: Text('暂无聊天记录')); } // 过滤搜索结果 final recentChats = snapshot.data!.docs.where((doc) { final chatData = doc.data() as Map<String, dynamic>; final partnerName = chatData['partnerUserInfo']['name'].toLowerCase(); final lastMessage = chatData['lastMessage'].toLowerCase(); return partnerName.contains(_searchQuery) || lastMessage.contains(_searchQuery); }).toList(); return ListView.builder( itemCount: recentChats.length, itemBuilder: (context, index) { final chatData = recentChats[index].data() as Map<String, dynamic>; return ListTile( leading: CircleAvatar( backgroundImage: NetworkImage(chatData['partnerUserInfo']['avatarUrl']), ), title: Text(chatData['partnerUserInfo']['name']), subtitle: Text(chatData['lastMessage']), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(DateFormat('HH:mm').format(chatData['timestamp'].toDate())), if (chatData['unreadCount'] > 0) Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: Text( chatData['unreadCount'].toString(), style: const TextStyle(color: Colors.white, fontSize: 12), ), ), ], ), onTap: () { // 跳转到聊天页面 Navigator.push( context, MaterialPageRoute( builder: (context) => ConversationPage( currentUserId: widget.currentUserId, partnerUserId: chatData['partnerUserId'], partnerUserInfo: chatData['partnerUserInfo'], ), ), ); // 点击后重置未读计数 FirebaseFirestore.instance .collection('users') .doc(widget.currentUserId) .collection('recent_chats') .doc(chatData['partnerUserId']) .update({'unreadCount': 0}); }, ); }, ); }, ), // 保留原有的添加联系人/搜索用户入口 floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (context) => const SearchUserPage())); }, child: const Icon(Icons.add), ), ); } }
步骤3:更新聊天会话(发送/接收消息时)
在ConversationPage的消息发送逻辑中,同步更新双方的recent_chats文档:
// 发送消息函数 void _sendMessage() async { if (_messageController.text.trim().isEmpty) return; final messageData = { 'senderId': widget.currentUserId, 'receiverId': widget.partnerUserId, 'content': _messageController.text.trim(), 'timestamp': FieldValue.serverTimestamp(), }; // 1. 添加消息到chatroom集合 await FirebaseFirestore.instance .collection('chatrooms') .doc(_getChatRoomId(widget.currentUserId, widget.partnerUserId)) .collection('messages') .add(messageData); // 2. 更新当前用户的recent_chats await _updateRecentChat( currentUserId: widget.currentUserId, partnerUserId: widget.partnerUserId, partnerUserInfo: widget.partnerUserInfo, lastMessage: _messageController.text.trim(), isSender: true, ); // 3. 更新对方用户的recent_chats final partnerUserDoc = await FirebaseFirestore.instance.collection('users').doc(widget.partnerUserId).get(); final partnerUserInfo = partnerUserDoc.data() as Map<String, dynamic>; await _updateRecentChat( currentUserId: widget.partnerUserId, partnerUserId: widget.currentUserId, partnerUserInfo: { 'name': widget.currentUserInfo['name'], 'avatarUrl': widget.currentUserInfo['avatarUrl'], }, lastMessage: _messageController.text.trim(), isSender: false, ); _messageController.clear(); } // 生成唯一的chatroom ID(确保双方使用同一个ID) String _getChatRoomId(String userId1, String userId2) { return userId1.compareTo(userId2) < 0 ? '$userId1-$userId2' : '$userId2-$userId1'; } // 更新recent_chats文档 Future<void> _updateRecentChat({ required String currentUserId, required String partnerUserId, required Map<String, dynamic> partnerUserInfo, required String lastMessage, required bool isSender, }) async { final recentChatRef = FirebaseFirestore.instance .collection('users') .doc(currentUserId) .collection('recent_chats') .doc(partnerUserId); final existingChat = await recentChatRef.get(); if (existingChat.exists) { await recentChatRef.update({ 'lastMessage': lastMessage, 'timestamp': FieldValue.serverTimestamp(), // 接收方增加未读计数,发送方重置为0 'unreadCount': isSender ? 0 : FieldValue.increment(1), }); } else { await recentChatRef.set({ 'partnerUserId': partnerUserId, 'partnerUserInfo': partnerUserInfo, 'lastMessage': lastMessage, 'timestamp': FieldValue.serverTimestamp(), 'unreadCount': isSender ? 0 : 1, }); } }
关键说明
- 所有最近聊天数据存储在Firebase中,天然支持跨设备同步,无需SharedPreferences
- 搜索功能通过过滤
recent_chats中的用户名和最后一条消息实现,也可扩展为同时搜索全部用户 recent_chats中存储对方用户基础信息,避免每次列表渲染重复查询users集合,提升性能
内容的提问来源于stack exchange,提问作者Abu Bakar Farooq
相关产品推荐
相关产品推荐

