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

Flutter Firestore聊天应用StateError:message字段不存在的解决

问题

开发基于Flutter的聊天应用,使用Firebase Firestore存储消息时,访问文档的message字段抛出以下错误:

Exception has occurred. StateError (Bad state: field "message" does not exist within the DocumentSnapshotPlatform)

相关代码(chat_page.dart)

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class ChatPage extends StatefulWidget {
  static const String screenRoute = "ChatPage";

  @override
  _ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  late User? signedInUser;
  TextEditingController _messageController = TextEditingController();

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

  void getSignedInUser() {
    final user = FirebaseAuth.instance.currentUser;
    setState(() {
      signedInUser = user;
    });
  }

  Future<void> sendMessage(String message, String receiverEmail) async {
    try {
      if (signedInUser != null) {
        final messageData = {
          'message': message,
          'senderId': signedInUser!.uid,
          'senderEmail': signedInUser!.email,
          'receiverEmail': receiverEmail,
          'timestamp': Timestamp.now(),
        };

        await FirebaseFirestore.instance.collection('messages').add(messageData);
        _messageController.clear();
      }
    } catch (e) {
      print('Error sending message: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
    final name = args['name'];
    final imageLink = args['ImageLink'];
    final lastSeenTimestamp = args['lastSeen'];

    // Convert Firestore Timestamp to DateTime
    DateTime? lastSeenDateTime;
    if (lastSeenTimestamp != null && lastSeenTimestamp is Timestamp) {
      lastSeenDateTime = lastSeenTimestamp.toDate();
    }

    return Scaffold(
      appBar: AppBar(
        leading: CircleAvatar(
          backgroundImage: NetworkImage(imageLink),
        ),
        title: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(name),
            Text(
              lastSeenDateTime != null ? formatLastSeen(lastSeenDateTime) : 'Last seen unknown',
              style: TextStyle(fontSize: 12),
            ),
          ],
        ),
      ),
      body: Column(
        children: [
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: FirebaseFirestore.instance.collection('messages').snapshots(),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return Center(
                    child: CircularProgressIndicator(backgroundColor: Colors.blue),
                  );
                }

                final messages = snapshot.data!.docs;
                return ListView.builder(
                  itemCount: messages.length,
                  itemBuilder: (context, index) {
                    final message = messages[index];
                    final messageText = message['message']; // Error occurs here
                    final senderId = message['senderId'];
                    final isMe = signedInUser?.uid == senderId;

                    return MessageReplay(
                      sendmessage: messageText,
                      isMe: isMe,
                    );
                  },
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _messageController,
                    decoration: InputDecoration(
                      hintText: 'Type a message...',
                    ),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.send),
                  onPressed: () {
                    final message = _messageController.text.trim();
                    final receiverEmail = 'receiver@example.com'; // Replace with the receiver's email address
                    if (message.isNotEmpty) {
                      sendMessage(message, receiverEmail);
                    }
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  String formatLastSeen(DateTime lastSeen) {
    Duration difference = DateTime.now().difference(lastSeen);
    if (difference.inDays > 0) {
      return 'Last seen ${difference.inDays} days ago';
    } else if (difference.inHours > 0) {
      return 'Last seen ${difference.inHours} hours ago';
    } else if (difference.inMinutes > 0) {
      return 'Last seen ${difference.inMinutes} minutes ago';
    } else {
      return 'Last seen just now';
    }
  }
}

class MessageReplay extends StatelessWidget {
  final String? sendmessage;
  final bool isMe;

  const MessageReplay({Key? key, this.sendmessage, required this.isMe})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: Column(
        crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
        children: [
          Material(
            elevation: 5,
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(30),
              bottomLeft: isMe ? Radius.circular(30) : Radius.circular(0),
              bottomRight: isMe ? Radius.circular(0) : Radius.circular(30),
              topRight: Radius.circular(30),
            ),
            color: isMe ? Colors.blue : Colors.white,
            child: Padding(
              padding: const EdgeInsets.all(12.0),
              child: Text(
                sendmessage ?? 'Message not found', // Handle null message
                style: TextStyle(fontSize: 15, color: isMe ? Colors.white : Colors.black),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

已尝试操作

  • 确认发送消息时message字段已正确添加到Firestore;
  • 验证Firestore集合和文档路径正确。

解决方案

1. 安全访问字段,避免直接索引取值

直接使用message['message']会在字段不存在时抛出异常,改用get()方法并设置默认值,或先检查字段是否存在:

修改StreamBuilder中的itemBuilder代码:

final message = messages[index];
// 方式1:使用get()方法并指定默认值
final messageText = message.get('message', defaultValue: '');
final senderId = message.get('senderId', defaultValue: '');
// 方式2:先检查字段存在性再取值
final messageData = message.data() as Map<String, dynamic>;
final messageText = messageData.containsKey('message') ? messageData['message'] : '';
final senderId = messageData.containsKey('senderId') ? messageData['senderId'] : '';

2. 过滤无效文档,减少无效数据加载

在Firestore查询中添加过滤条件,只获取包含message和senderId字段的文档:

stream: FirebaseFirestore.instance
    .collection('messages')
    .where('message', isNotEqualTo: null)
    .where('senderId', isNotEqualTo: null)
    .snapshots(),

3. 清理或修复Firestore中的无效数据

如果Firestore中存在没有message字段的旧文档,直接在Firebase控制台删除这些无效文档,或批量更新补充缺失字段。

4. 引入强类型数据模型(可选,长期优化)

创建Message类封装数据,通过fromDocumentSnapshot方法安全解析文档,提升代码可维护性:

class Message {
  final String message;
  final String senderId;
  final String senderEmail;
  final String receiverEmail;
  final Timestamp timestamp;

  Message({
    required this.message,
    required this.senderId,
    required this.senderEmail,
    required this.receiverEmail,
    required this.timestamp,
  });

  factory Message.fromDocumentSnapshot(DocumentSnapshot doc) {
    final data = doc.data() as Map<String, dynamic>;
    return Message(
      message: data['message'] ?? '',
      senderId: data['senderId'] ?? '',
      senderEmail: data['senderEmail'] ?? '',
      receiverEmail: data['receiverEmail'] ?? '',
      timestamp: data['timestamp'] ?? Timestamp.now(),
    );
  }
}

使用时修改itemBuilder:

final message = Message.fromDocumentSnapshot(messages[index]);
final messageText = message.message;
final senderId = message.senderId;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 21:55:55