Flutter Firebase开发中toDate()空调用报错如何解决
问题报错
运行Flutter+Firebase开发的VIP聊天应用真机调试时,Widgets库抛出如下NoSuchMethodError异常:
======== Exception caught by widgets library =======================================================
The following NoSuchMethodError was thrown building:
The method 'toDate' was called on null.
Receiver: null
Tried calling: toDate()
关联代码
首页 HomePage 实现
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:vip_chat/chat_room.dart'; import 'package:vip_chat/fireBaseHelper.dart'; import 'package:vip_chat/model/chatRoom_model.dart'; import 'package:vip_chat/search_page.dart'; import 'model/messages_model.dart'; import 'model/user_model.dart'; class HomePage extends StatefulWidget { final userModel usermodel; final User firebasebaseUser; const HomePage({super.key, required this.usermodel, required this.firebasebaseUser}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text('VIP Chat App'), ),body: SafeArea( child: Container( child: StreamBuilder( stream: FirebaseFirestore.instance.collection('chatrooms').where('participants.${widget.usermodel.uid}',isEqualTo: true).snapshots(), builder: (context,snapshot){ if(snapshot.connectionState==ConnectionState.active){ if(snapshot.hasData){ QuerySnapshot dataSnapshot=snapshot.data as QuerySnapshot; return ListView.builder( itemCount: dataSnapshot.docs.length,itemBuilder: (context,index){ // 报错直接触发点 MessageModel currentMessage=MessageModel.fromMap(dataSnapshot.docs[index].data() as Map<String,dynamic>); ChatRoomModel chatroommodel=ChatRoomModel.fromMap(dataSnapshot.docs[index].data()as Map<String,dynamic>); Map<String,dynamic>participants=chatroommodel.participants!; List<String>partkeys=participants.keys.toList(); partkeys.remove(widget.usermodel.uid); return FutureBuilder(future: FireBaseHelper.getUsermodel(partkeys[0]),builder: (context,userData){ if(userData.connectionState==ConnectionState.done){ if(userData.data!=null){ // 附带类型强转bug位置 userModel targetUser=userData as userModel; return ListTile( onTap: (){ Navigator.push(context, MaterialPageRoute(builder: (context){ return ChatRoomPage(targetUser: targetUser, chatroom: chatroommodel, usermodel: widget.usermodel, firebaseuser: widget.firebasebaseUser); })); }, title: Text(targetUser.fullName.toString()), subtitle: Text(chatroommodel.lastMessage.toString()), leading: CircleAvatar(backgroundImage: NetworkImage(targetUser.profilepic.toString()),), ); } else{ return Container(); } }else{ return Container(); } },); },); } else if(snapshot.hasError){ return Center(child: Text('An error occurred!')); } else{ return Center( child: Text('No chats'), ); } }else{ return Center(child: CircularProgressIndicator(),); } }, ), ), ), floatingActionButton: FloatingActionButton( onPressed: (){ Navigator.push(context, MaterialPageRoute(builder: (context){return SearchPage(usermodel: widget.usermodel, firebaseuser:widget.firebaseuser );})); }, child: Icon(Icons.search), ), ); } }
MessageModel 消息模型实现
import 'package:flutter/material.dart'; class MessageModel{ String?messageid; String? sender; String?text; bool?seen; DateTime?createdon; MessageModel({this.sender,this.text,this.seen,this.createdon,this.messageid}); MessageModel.fromMap(Map<String,dynamic>map){ messageid=map['messageid']; sender=map['sender']; text=map['text']; seen=map['seen']; // 报错根源位置,无空判断 createdon=map['createdon'].toDate(); } Map<String,dynamic>toMap(){ return{ 'messageid':messageid, 'sender':sender, 'text':text, 'seen':seen, 'createdon':createdon }; } }
ChatRoomPage 聊天室页面实现
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:vip_chat/main.dart'; import 'package:vip_chat/model/chatRoom_model.dart'; import 'package:vip_chat/model/messages_model.dart'; import 'model/user_model.dart'; class ChatRoomPage extends StatefulWidget { final userModel targetUser; final ChatRoomModel chatroom; final userModel usermodel; final User firebaseuser; const ChatRoomPage({super.key, required this.targetUser, required this.chatroom, required this.usermodel, required this.firebaseuser}); @override State<ChatRoomPage> createState() => _ChatRoomPageState(); } class _ChatRoomPageState extends State<ChatRoomPage> { TextEditingController messagecontroller=TextEditingController(); void sendMessage() async{ String msg=messagecontroller.text.trim(); messagecontroller.clear(); if(msg!=''){ MessageModel newMessage=MessageModel( messageid: uuid.v1(), sender: widget.usermodel.uid, createdon: DateTime.now(), text: msg, seen: false ); FirebaseFirestore.instance.collection('chatrooms').doc(widget.chatroom.chatroomid).collection('messages').doc(newMessage.messageid).set(newMessage.toMap()); print('Message Sent'); widget.chatroom.lastMessage=msg; FirebaseFirestore.instance.collection('chatrooms').doc(widget.chatroom.chatroomid).set(widget.chatroom.toMap()); print('last message created'); } } @override Widget build(BuildContext context) { return Scaffold( appBar:AppBar( title: Row(children: [ CircleAvatar(backgroundImage: NetworkImage(widget.targetUser.profilepic.toString()), ), SizedBox(width: 10,), Text(widget.targetUser.fullName.toString()) ],), ), body: SafeArea(child: Container( child: Column(children: [Expanded(child: Container( child: StreamBuilder( stream: FirebaseFirestore.instance.collection('chatrooms').doc(widget.chatroom.chatroomid).collection('messages').orderBy('createdon',descending: true).snapshots(), builder: (context,snapshot){ if(snapshot.connectionState==ConnectionState.active){ if(snapshot.hasData){ QuerySnapshot dataSnapshot=snapshot.data as QuerySnapshot; return ListView.builder(reverse: true, itemCount: dataSnapshot.docs.length,itemBuilder: (context,index){ MessageModel currentMessage=MessageModel.fromMap(dataSnapshot.docs[index].data() as Map<String,dynamic>); return Padding( padding: EdgeInsets.symmetric(horizontal: 10,vertical: 2), child: Row( mainAxisAlignment: currentMessage.sender==widget.usermodel.uid?MainAxisAlignment.end:MainAxisAlignment.start, children: [ Container( margin: EdgeInsets.symmetric(vertical: 2), decoration:BoxDecoration( borderRadius: BorderRadius.circular(10), color: currentMessage.sender==widget.usermodel.uid?Colors.lightGreenAccent:Colors.orange ),child: Padding( padding: const EdgeInsets.all(8.0), child: Text(currentMessage.text.toString(),style: TextStyle(fontSize: 15),), )), ], ), ); },); } else if(snapshot.hasError){ return Center(child: Text('An error occurred!')); } else{ return Center( child: Text('say hi to your new friend'), ); } }else{ return Center(child: CircularProgressIndicator(),); } }, ), )), Padding( padding: EdgeInsets.symmetric(horizontal: 10,vertical: 10), child: Container(color: Colors.grey.shade200, child: Row( children: [Flexible(child: TextField( controller:messagecontroller, maxLines: null, decoration: InputDecoration( border: InputBorder.none, hintText: 'Enter your message here', ), )), IconButton(onPressed: (){sendMessage();}, icon: Icon(Icons.send),color: Colors.red,), ], ),), )],), ),),); } }
错误根因
- 首页StreamBuilder拉取的是
chatrooms根集合的文档数据,这类文档存储聊天室基础信息、参与者、最后一条消息内容,不存在createdon字段,但代码多余地将chatrooms根文档数据传入MessageModel.fromMap()解析,取map['createdon']时拿到null,直接调用toDate()触发空指针异常。且解析出来的currentMessage变量在首页完全没有被使用,属于无效代码。 MessageModel.fromMap方法没有对createdon字段做空安全校验,后续拉取messages子集合数据时,如果遇到字段缺失的脏数据,同样会触发同类报错。- 首页FutureBuilder中存在类型强转错误:直接将
AsyncSnapshot类型的userData强转为userModel,会触发类型转换异常,属于附带代码bug。
修复步骤
- 删除首页ListView.builder中无效的MessageModel解析代码,即删除
MessageModel currentMessage=MessageModel.fromMap(dataSnapshot.docs[index].data() as Map<String,dynamic>);这一行,直接消除首页的报错触发点。 - 修改MessageModel的fromMap方法,增加空判断逻辑,兼容字段缺失场景:
MessageModel.fromMap(Map<String,dynamic>map){ messageid=map['messageid']; sender=map['sender']; text=map['text']; seen=map['seen']; // 空安全处理:字段非空时再调用toDate,否则赋值null createdon = map['createdon'] != null ? (map['createdon'] as Timestamp).toDate() : null; }
- 修正首页FutureBuilder中的类型强转错误,将
userModel targetUser=userData as userModel;改为:
userModel targetUser = userData.data as userModel;
- 打开Firebase控制台检查
chatrooms集合、chatrooms/{chatroomid}/messages子集合下的测试数据,清理手动创建的缺失必要字段的脏文档,避免历史数据触发异常。
内容的提问来源于stack exchange,提问作者Vipin
相关产品推荐
相关产品推荐

