In the init, of my application I register a stream to one of the documents stored in firestore. Later I update a timestamp field in the same document. I should be getting one callback from the stream since there is only 1 update.
However, I am getting 2 callbacks -
Where that updated field is null
Where that updated field has the correct updated value
Any ideas why?
CollectionReference collectionReference = FIRESTORE.collection("users");
if(streamSub == null) {
streamSub = collectionReference.document(documentID).snapshots().listen((onData){
onData.data.forEach((k,v) => debugPrint(k + " = " + v.toString()));
});
}
//Update field
Firestore.instance
.collection("users")
.document(documentID)
.updateData({"Time" : FieldValue.serverTimestamp() })
You're getting two callbacks because of your use of FieldValue.serverTimestamp(). That value is actually a token that gets sent to Firestore servers, where the timestamp is determined and finally written to the database. Locally on the client, the value isn't known at the time of the write, however, a write of the document still happens in the local cache.
Your listener is first getting the local cache write (before the timestamp is known), then again from the server after the timestamp is known. You can look at the snapshot metadata to figure out the source of the data if that's important to you.
Related
I have the following Firestore stream:
FirebaseFirestore.instance.collection("users").orderBy("timestamp", descending: true).snapshots().listen((value) async{
// here i noticed if doc was added or updated so the stream will take action to all docs
in that collection
});
For example if i write .set() to same collection like following:
.set({
name : 'Alex'
});
and I had another doc with the field name 'jack' then I used the following in the previous stream:
stream...{
if(value['name]=='Alex'){
print('ok');
}
if(value['name]=='jack'){
print('ok'); // ok here should print 'ok' if it first snapshot ,
but it will always print 'ok' whatever the new doc equal
my condition or not
}
}
How can I only listen to doc that newly added or updated or changed ?
As #Dharmaraj recommended, docChanges() method informs us of all the changes that have occurred in our database.
docChanges() method should be used on the snapshot parameter. Since that gives an array of document changes, we iterate over with a loop. If you make a console.log of the local variable of the loop, you will see that this returns each change (in the form of an object).
Example:
db.collection('recipes').onSnapshot(snapshot => {
// console.log(snapshot.docChanges());
snapshot.docChanges().forEach(change => {
console.log(change);
});
});
Do not forget that docChanges() should always be a function and never an array.
Additionally, you can add server timestamp. You can set a server timestamp in a field in your document to track when the server receives the update.
Each field receives the same server timestamp value when several timestamp fields are updated during a transaction.
My StreamBuilder didn't refresh after changes to the stream, and also the stream does not update too, when fetching data from Firebase and store it to local SQLite database;
And here is my code for listen to data changes from Firebase and then write those new chats to local:
/// LISTEN TO THE STREAM FROM FIREBASE AND THEN WRITE THE NEW MESSAGES TO THE LOCAL DATASE
// Fetch normal messages
firestore.fetchMessagesStream(chatRoomId).listen((event) async {
for (QueryDocumentSnapshot message in event.docs) {
_database.insert(
'Messages',
message.data()!,
conflictAlgorithm: sql.ConflictAlgorithm.replace,
);
}
});
// Fetch calls
firestore.fetchCallsStream(chatRoomId).listen((event) async {
for (QueryDocumentSnapshot call in event.docs) {
_database.insert(
'Calls',
call.data()!,
conflictAlgorithm: sql.ConflictAlgorithm.replace,
);
// Fetch posts
firestore.fetchPostsStream(chatRoomId).listen((event) async {
for (QueryDocumentSnapshot post in event.docs) {
_database.insert(
'Posts',
post.data()!,
conflictAlgorithm: sql.ConflictAlgorithm.replace,
);
}
});
And here the code for fetching data from the Local SQLite database:
/// STREAM FOR LISTENING THE CHANGES IN THE LOCAL DATABASE
Rx.combineLatest3(
_database.query(
'Messages',
where: 'chatRoomId = ?',
whereArgs: [chatRoomId],
).asStream(), // Returns the stream of the Messages Table in local databases
_database.query(
'Posts',
where: 'chatRoomId = ?',
whereArgs: [chatRoomId],
).asStream(), // Returns the stream of the Posts Table in local databases
_database.query(
'Calls',
where: 'chatRoomId = ?',
whereArgs: [chatRoomId],
).asStream(), // Returns the stream of the Calls Table in local databases
(List<Map<String, dynamic>> streamingMessages,
List<Map<String, dynamic>> streamingPosts,
List<Map<String, dynamic>> streamingCalls) {
/// VERY IMPORTANT: THE FOLLOWING PRINT STATEMENT WILL BE PRINT OUT EVERYTIME A NEW CHAT ARRIVE
/// VERY IMPORTANT: THE FOLLOWING PRINT STATEMENT WILL BE PRINT OUT EVERYTIME A NEW CHAT ARRIVE
/// VERY IMPORTANT: THE FOLLOWING PRINT STATEMENT WILL BE PRINT OUT EVERYTIME A NEW CHAT ARRIVE
/// VERY IMPORTANT: THE FOLLOWING PRINT STATEMENT WILL BE PRINT OUT EVERYTIME A NEW CHAT ARRIVE
/// VERY IMPORTANT: THE FOLLOWING PRINT STATEMENT WILL BE PRINT OUT EVERYTIME A NEW CHAT ARRIVE
print('MySqlite: chat stream changes!');
final List<dynamic> contents = [...streamingMessages, ...streamingPosts, ...streamingCalls];
return contents;
},
);
}
The expected timeline when sent a new message will be:
User sent a message --> trigger changes in Firebase --> trigger the LOCAL SQLite to add new data --> Because our SQLite have added new chats, so our ChatScreen should refresh & also a new debug message: 'MySqlite: chat stream changes!' should be printed out again in the console since the ChatScreen is listening to the SQLite Database Stream.
But the actual result is:
User sent a message --> trigger changes in Firebase successfully --> but I DIDN'T see the screen being refreshed NOR new debug message in the console...
I've been struggle with this issue for days, and I don't know why the result is not what I want, if I let the ChatScreen to directly listen to Firebase, it works!*
UPDATE:
I just found out that if I rebuild the ChatScreen (pop the screen, and then open it again), or setState when sending a new message, I can see the new message, it proves that the Message did go into the SQLite database after sent, but it just did not trigger the StreamBuilder. So it might be something wrong with the Fetching Stream.
ANSWER:
I just found out that the SQLite.query function cannot be fetched as a Stream, and I thought I can by using "asStream" method, but this does not do anything, it is a missed feature that SQLite package didn't implement yet, so I add the sqlbrite package that works as a wrapper of the original SQLite package, and it has some additional feature such as querying data as a Stream. ^_^
I just found out that the SQLite.query function cannot be fetched as a Stream, and I thought I can by using "asStream" method, but this does not do anything, it is a missed feature that SQLite package didn't implement yet, so I add the sqlbrite package that works as a wrapper of the original SQLite package, and it has some additional feature such as querying data as a Stream. ^_^
I want to only listen to changes in a document in firebase, but now it seems it is listening every moment although there is no changes. I can see that because when I print it to the console it never stops.
FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.snapshots()
.listen((DocumentSnapshot documentSnapshot) {
Map firestoreInfo = documentSnapshot.data();
setState(() {
name = firestoreInfo['name'];
});
});
Could anyone help here? Also, it seems there is no "documentChanges" method for me I can call on the document snapshot data.
When you attach a listener to Firestore it initially fires with the current value of the document from the database. It then fires whenever the document changes. There is no way to skip the initial value of a listener.
A common way to get the behavior you want is to:
Store a lastUpdated timestamp field in the user document(s).
Use a query to only return documents where lastUpdated is after "now".
To perform such a query on a document with a specific ID, you'll need to use a condition on FieldPath.documentId.
I needed in my app firebase (server) time millisecond. because my local time will be change by mobile date time setting,
i will be see this question by not so helpful,
Flutter Firestore Server side Timestamp
var timestamp=FieldValue.serverTimestamp();
print("timestamp"+timestamp.toString());
but they print below value
====>Instance of 'FieldValue'
you should use
timestamp() {
Firestore.instance.collection('timestamp').document('time').setData({
"timestamp": FieldValue.serverTimestamp(),
});
}
Before reading the timestamp you have to set it to the server since you are generating it from there. According to this
serverTimestamp() Returns a sentinel for use with set() or update() to include a server-generated timestamp in the written data.
so first set it
Firestore.instance.collection('timestamp').document('docId').setData({'time' : FieldValue.serverTimestamp()});
then get it using basic firestore query. Finally, check out this answer on the details of the why.
For more general way of getting timestamp
getTimestamp() async{
await Firestore.instance.collection('timestamp').document('docId').setData({'time' : FieldValue.serverTimestamp()});
DocumentSnapshot doc = await Firestore.instance.collection('timestamp').document('docId').get();
Timestamp timestamp = doc['time'];
var timestampInMilliSeconds = timestamp.millisecondsSinceEpoch;
return timestampInMilliSeconds;
/* here you can call setState to your own variable before returning and keep in mind this method is async so it will return a Future. */
}
I have a question regarding QuerySnapshot. For example, lets say I have a chat app. To keep the discussion updated I use a StreamBuilder connected to Firestore. I use a querySnapshot to retrieve all the documents in the collection "messages" and every time I add a message a new Query Snapshot is triggered with the new message and all the previous documents. So here is my question, If my collection "messages" contain 10 documents, the first time I have to get all document so I read 10 documents. Next I add a messag, I now have 11 documents in my collection and the querySnapshot will then return the 11 documents even if I only need the new one. So in the end, will it count as 11 documents read (10 + the new one ) or 21 (10 +11 ) ? If it is the lattest, is there a way to only get the new document instead of all the documents ?
Thanks in advance.
It depends if you have setup a listener .addSnapshotListener or if you have just used .getdocument. If you have set up a listener it will read only new or changed documents from Firestore and then merges it with the locally cached data. It will then present your app with the full 11 documents again though. 10 from the local cache, 1 loaded new. You do have the option to only get the changes, see below. If you didn't set up a listener you can just changed .getdocument with .addSnapshotListener, the rest of the code should be the same. Don't forget to detach the listener when it"s not longer needed though.
db.collection("cities").whereField("state", isEqualTo: "CA")
.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
print("New city: \(diff.document.data())")
}
if (diff.type == .modified) {
print("Modified city: \(diff.document.data())")
}
if (diff.type == .removed) {
print("Removed city: \(diff.document.data())")
}
}
}
For as long as a listener is attached to a query, no documents will be re-read from the server as long as they are unchanged. The listener is delivered the entire set for every change, but they are being delivered from memory if they are not changed. You are only getting the changes with each callback, and you can even check to see exactly what changed each time.