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

Flutter聊天页面Firestore报错:'isDocument()': is not true 求助

Flutter Firestore Stream 断言错误:'isDocument()': is not true 解决方案

错误原因

  1. chatDocId类型错误:初始化新增聊天文档时,将DocumentReference对象直接赋值给chatDocId,后续调用chatDocId.toString()会生成类似DocumentReference(chats/xxx)的无效字符串,传递给doc()方法时触发Firestore内部断言失败。
  2. 聊天记录查询逻辑缺陷:使用isEqualTo匹配无序Map对象,因Map键的顺序会影响匹配结果,导致无法正确查询已存在的聊天记录,可能重复创建文档。

修复步骤

1. 修正chatDocId赋值逻辑

新增文档时,提取DocumentReference的id属性作为文档ID,而非直接保存对象:

final newDoc = await chats.add({
  "users": {currentUserUid: null, friendUid: null},
});
chatDocId = newDoc.id;

2. 优化聊天记录查询方式

改用多条件查询验证两个用户ID存在于usersMap中,避免Map顺序影响匹配:

final snapshot = await chats
    .where("users.$currentUserUid", isNull: true)
    .where("users.$friendUid", isNull: true)
    .limit(1)
    .get();

3. 避免无效Stream请求

在chatDocId未初始化完成前,不创建Stream,同时在UI中显示加载状态:

chatDocId == null
    ? const Center(child: CircularProgressIndicator())
    : StreamBuilder<QuerySnapshot>(...)

完整修复代码

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_bubble/bubble_type.dart';
import 'package:flutter_chat_bubble/chat_bubble.dart';
import 'package:flutter_chat_bubble/clippers/chat_bubble_clipper_6.dart';

class Chat extends StatefulWidget {
  Chat({super.key, required this.friendUid, required this.friendName});
  final String friendUid;
  final String friendName;

  @override
  State<Chat> createState() => _ChatState(friendUid, friendName);
}

class _ChatState extends State<Chat> {
  CollectionReference chats = FirebaseFirestore.instance.collection("chats");
  final String friendUid;
  final String friendName;
  final String currentUserUid = FirebaseAuth.instance.currentUser!.uid;
  String? chatDocId;
  final TextEditingController _controller = TextEditingController();

  _ChatState(this.friendUid, this.friendName);

  @override
  void initState() {
    super.initState();
    _initChatDoc();
  }

  void _initChatDoc() async {
    final snapshot = await chats
        .where("users.$currentUserUid", isNull: true)
        .where("users.$friendUid", isNull: true)
        .limit(1)
        .get();

    if (snapshot.docs.isNotEmpty) {
      setState(() {
        chatDocId = snapshot.docs.single.id;
      });
    } else {
      final newDoc = await chats.add({
        "users": {currentUserUid: null, friendUid: null},
      });
      setState(() {
        chatDocId = newDoc.id;
      });
    }
  }

  void sendMessage(String msg) {
    if (msg.isEmpty || chatDocId == null) return;
    chats.doc(chatDocId).collection("messages").add({
      "createdOn": FieldValue.serverTimestamp(),
      "uid": currentUserUid,
      "msg": msg
    }).then((_) {
      _controller.clear();
    });
  }

  bool isSender(String uid) {
    return uid == currentUserUid;
  }

  Alignment getAlignment(String uid) {
    return isSender(uid) ? Alignment.topRight : Alignment.topLeft;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(friendName)),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisSize: MainAxisSize.max,
          children: [
            Expanded(
              child: chatDocId == null
                  ? const Center(child: CircularProgressIndicator())
                  : StreamBuilder<QuerySnapshot>(
                      stream: chats
                          .doc(chatDocId)
                          .collection("messages")
                          .orderBy("createdOn", descending: true)
                          .snapshots(),
                      builder: (context, snapshot) {
                        if (snapshot.hasError) {
                          return const Center(child: Text("加载出错"));
                        }
                        if (snapshot.connectionState == ConnectionState.waiting) {
                          return const Center(child: CircularProgressIndicator());
                        }
                        final messages = snapshot.data?.docs ?? [];
                        return ListView.builder(
                          reverse: true,
                          itemCount: messages.length,
                          itemBuilder: (context, index) {
                            final data = messages[index].data() as Map<String, dynamic>;
                            final msg = data["msg"] ?? "";
                            final uid = data["uid"] ?? "";

                            return ChatBubble(
                              clipper: ChatBubbleClipper6(
                                nipSize: 0,
                                radius: 0,
                                type: isSender(uid) ? BubbleType.sendBubble : BubbleType.receiverBubble,
                              ),
                              alignment: getAlignment(uid),
                              margin: const EdgeInsets.only(top: 20),
                              backGroundColor: isSender(uid) ? const Color(0xFF08C187) : const Color(0xffE7E7ED),
                              child: Container(
                                constraints: BoxConstraints(
                                  maxWidth: MediaQuery.of(context).size.width * 0.7,
                                ),
                                child: Text(
                                  msg,
                                  style: TextStyle(
                                    color: isSender(uid) ? Colors.white : Colors.black,
                                  ),
                                  maxLines: 100,
                                  overflow: TextOverflow.ellipsis,
                                ),
                              ),
                            );
                          },
                        );
                      },
                    ),
            ),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: "输入消息...",
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                IconButton(
                  onPressed: () => sendMessage(_controller.text.trim()),
                  icon: const Icon(Icons.send),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

额外优化建议

  • 将users字段改为List<String>数组类型,查询时使用arrayContains结合逻辑判断,更符合Firestore最佳实践,彻底避免Map顺序问题。
  • 对FirebaseAuth.instance.currentUser添加空值判断,防止用户未登录时出现崩溃。

内容的提问来源于stack exchange,提问作者intel44

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 19:50:33