You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter+Firestore实现无SharedPreferences的最近聊天联系人列表

实现Flutter聊天应用的最近聊天联系人列表(无需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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 15:05:20