Time Field serverTimestamp() in cloud Firestore returns null on the first snapshot

1,310

I've also encountered the same problem. and after a wide search, I've found this article very useful. It helps to understand the issue and the flow causes the problem.

Using DateTime.now() is not a solution since the main purpose of using the FieldValue.serverTimestamp() is to sync times between different devices, which DateTime.now() does not solves.

now, regarding the NULL issue with the FieldValue: I've also used sortTime.toDate().toString() as suggested Here , but getting NULL on the timeStamp, because:

  1. when sending the message, a write request is sent to the server, but the data isn't available yet.
  2. builder is trying to pull the data and gets null.
  3. data is available, and the NULL replaces with the real data. to solve that few milliseconds glitch, I've tried multiple solutions, but found this one the simplest: put an empty string until that data is available, the UI will be empty for about 0.5 seconds, but from my experience, it still looks good.

TL:DR

final sortTime = snapshot.data.documents[index].data()["sortTime"];
String serverTime = sortTime == null ? "" : sortTime.toDate().toString();

consider this Dart code as a brief example to explain with:

sending Msg function:

/// on pressed action (send to firebase, and show message)
/// consider the messageController gets the input from the user
sendMessage(MyUser myUser) async {
    if (messageController.text.isNotEmpty){
      Map<String, dynamic> chatMessage = {
        "senderUid" : myUser.userId,
        "text" : messageController.text,
        "sortTime" : FieldValue.serverTimestamp(),
      };
      messageController.text = "";
      await addConversationMessages(widget.chatRoomId, chatMessage);
    }
}

Future addConversationMessages(String chatRoomId, chatMessage) async {
  await chatsCollection
      .doc(chatRoomId)
      .collection("chats")
      .add(chatMessage);
}

get the messages

getConversationMessages(String chatRoomId) async {
  return await chatsCollection
      .doc(chatRoomId)
      .collection("chats")
      .orderBy("sortTime", descending: true)
      .snapshots();
}
Stream chatMessagesStream;
void initState(){
    getConversationMessages(widget.chatRoomId)
      .then((value){
        setState(() {
          chatMessagesStream = value;
        });
    });
    super.initState();
  }
Widget ListViewMessages(MyUser myUser){
    return StreamBuilder(
        stream: chatMessagesStream,
        builder: (context, snapshot){
          if(!snapshot.hasData){
            return Center(
                child: CircularProgressIndicator(
                  valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF087E8B)),
                )
            );
          } else {
            return ListView.builder(
                reverse: true,
                itemCount: snapshot.data.documents.length,
                itemBuilder: (context, index) {
                  final msgText = snapshot.data.documents[index].data()["text"];
                  final msgUid = snapshot.data.documents[index]
                      .data()["senderUid"];
                  final sortTime = snapshot.data.documents[index].data()["sortTime"];
                  String serverTime = sortTime == null ? "" : sortTime.toDate().toString();
                  return _buildMessage(
                      msgText, serverTime, msgUid);
                }
            );
          }
        },
    );
  }
Share:
1,310
Panagiss
Author by

Panagiss

Undergraduate IT Student at the Department of Informatics and Telematics in Harokopio University of Athens(HUA)

Updated on December 24, 2022

Comments

  • Panagiss
    Panagiss over 1 year

    Im reading some data(messages) from Cloud Firestore and in a document i have a timestamp field for time. I have a Stream:

    Stream<QuerySnapshot> get chats {
        return chatCollection.document(roomid).collection("messages").snapshots();
      }
    

    for getting the messages if a an update occurs in my database (ex New message).

    So when i start the app it reads all the data(messages) from the DB and i print them in my app. Here is what my console printing the messages every time i get a new snapshot:

    D/ViewRootImpl@a0e5145[MainActivity]( 3045): MSG_RESIZED: frame=Rect(0, 0 - 1440, 2560) ci=Rect(0, 96 - 0, 1164) vi=Rect(0, 96 - 0, 1164) or=1
    D/ViewRootImpl@a0e5145[MainActivity]( 3045): Relayout returned: old=[0,0][1440,2560] new=[0,0][1440,2560] result=0x1 surface={valid=true 492514541568} changed=false
    I/flutter ( 3045): DEBUG: TEST-MESSAGE what??? 2020-10-09 22:30:12.249
    I/flutter ( 3045): DEBUG: TEST-MESSAGE λολ 2020-10-09 21:59:58.212
    I/flutter ( 3045): DEBUG: TEST-MESSAGE gamieste 2020-10-09 22:33:10.902
    I/flutter ( 3045): DEBUG: TEST-MESSAGE holly 2020-10-09 22:26:39.672
    I/flutter ( 3045): DEBUG: TEST-MESSAGE γφ 2020-10-09 22:08:47.617
    I/flutter ( 3045): DEBUG: TEST-MESSAGE ελα 2020-10-09 22:13:38.167
    I/flutter ( 3045): DEBUG: TEST-MESSAGE see re 2020-10-09 22:29:14.277
    I/flutter ( 3045): DEBUG: TEST-MESSAGE ιξι 2020-10-09 22:10:07.442
    I/flutter ( 3045): DEBUG: TEST-MESSAGE ολα καλα ρε 2020-10-09 22:05:00.703
    I/flutter ( 3045): DEBUG: TEST-MESSAGE what??? 2020-10-09 22:30:12.249
    I/flutter ( 3045): DEBUG: TEST-MESSAGE λολ 2020-10-09 21:59:58.212
    I/flutter ( 3045): DEBUG: TEST-MESSAGE gamieste 2020-10-09 22:33:10.902
    I/flutter ( 3045): DEBUG: TEST-MESSAGE holly fuck 2020-10-09 22:26:39.672
    I/flutter ( 3045): DEBUG: TEST-MESSAGE γφ 2020-10-09 22:08:47.617
    I/flutter ( 3045): DEBUG: TEST-MESSAGE ελα μου 2020-10-09 22:13:38.167
    

    So no problem on that. The problem occurs when i add a new document(message) in my DB from my code, then immediately it gets the update from the DB and i have the new snapshot with the new list of messages. But for about 0.5 sec i get an error on the screen oh my android and then it gets normal and has loaded all messages correctly. The error is a null pointer for a specific field of the document. TIME. Time is timestamp field in my Cloud Firestore DB.

    So here is the error:

    The following NoSuchMethodError was thrown building:
    The method 'toDate' was called on null.
    Receiver: null
    Tried calling: toDate()
    
    When the exception was thrown, this was the stack:
    #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
    #1      _ChatScreenState._buildMessage (package:flutter_firebase_chat_app/screens/chat/chat_screen.dart:48:43)
    #2      _ChatScreenState.build.<anonymous closure>.<anonymous closure> (package:flutter_firebase_chat_app/screens/chat/chat_screen.dart:226:40)
    #3      SliverChildBuilderDelegate.build (package:flutter/src/widgets/sliver.dart:448:22)
    #4      SliverMultiBoxAdaptorElement._build.<anonymous closure> (package:flutter/src/widgets/sliver.dart:1136:67)
    #5      _HashMap.putIfAbsent (dart:collection-patch/collection_patch.dart:140:29)
    #6      SliverMultiBoxAdaptorElement._build (package:flutter/src/widgets/sliver.dart:1136:26)
    #7      SliverMultiBoxAdaptorElement.performRebuild.processElement (package:flutter/src/widgets/sliver.dart:1082:66) 
    

    You will see that its just a null exception. The thing is why im getting a null for a Time field only when i upload a message. And the thing is that after that the message comes again from another snapshot with no-null value!! If you look on the error code segment i have printed the whole object and in the top right corner you will see that time field has null value. I made an if statement to print the whole object whenever time field is null.

    So at last the thing i think is causing this is that time field is a timestamp and when i upload the data i give it a value of ServerTimestamp(). Code here:

    Future sendMessage(Message message) async {
        if (message.senderId != AuthService.myUid) return null;
        return await chatCollection.document(roomid).collection("messages").add({
          "message": message.text,
          "sender": message.senderId,
          "receiver": message.receiverId,
          "time": FieldValue.serverTimestamp(),
          "isLiked": message.isLiked,
          "unread": message.unread,
        });
      }
    

    As you can see the Time field is the only one that takes that FieldValue.serverTimestamp() , so i think that somehow i get snapshot of the document before the Timestamp value to field time has been assigned. And after i get it all (with the Time field being no-Null)

    Any ideas?

  • Panagiss
    Panagiss over 3 years
    it solves it of course. But i don't get why serverTimestamp has this problem!
  • Panagiss
    Panagiss over 3 years
    i will leave the post unanswered till i find out about serverTimestamp()
  • Mohammad I
    Mohammad I over 3 years
    The following document may answer to your query: medium.com/firebase-developers/…
  • Michael B.
    Michael B. about 2 years
    Not using flutter, but your answer brought me to some other docs for the JS lib and maybe this also works in flutter: the doc.data() function accepts a serverTimestamp param, with estimate/previous the timestamp will not be null in between updates that use serverTimestamp