How can I listen only to doc that changed in Firestore stream? - firebase

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.

Related

Listen only to changes in document instead of all the time

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.

Using onWrite Trigger for Realtime database in Firebase function

I designed a flutter application using dart and firebase backend. I want to get triggered when a user creates, delete, or updates anything in the database. I understood onCreate,onRemoved, and onUpdate methods.
I wrote a javascript code in the firebase cloud function. I need to send a notification when a trigger happens. I don't want to use 3 different triggers. I want to use onWrite trigger only.
This is my database.
There are four departments called comp, civil, elect, and mech. I want to get triggered when database changes happen in a single onWrite trigger!
My First question is, I want to differentiate whether it is created or delete or update from onWrite trigger. I know it will trigger for all 3 events. But how can I differentiate it?
This is my code for onCreate...
exports.noticeAddedComp = functions.database.ref('main/notices/comp/{id}').onCreate( async evt => {
var token = ['dxnfr3dq6_Y:APA91bHavtoP62l296qzVoBhzQJbMFA']
const payload = {
notification:{
title : 'Message from Cloud',
body : 'This is your body',
sound : 'default'
},
data:{
message : 'Thi is new Push',
click_action : 'FLUTTER_NOTIFICATION_CLICK',
}
};
await admin.messaging().sendToDevice(token,payload);
});
Above code is working. But it is for onCreate.
My second question is, Do I need to write four onWrite trigger codes for four departments?
You can use the change.before and change.after properties to determine whether the node was created, deleted, or updated in a onWrite handler:
exports.noticeAddedComp = functions.database.ref('main/notices/comp/{id}')
.onWrite( async (change, context) => {
if (!change.before.exists()) console.log("New node: "+change.after.key);
if (!change.after.exists()) console.log("Deleted node: "+change.before.key);
if (change.before.exists() && change.after.exists()) console.log("Updated node: "+change.after.key)
...
});
You can attach a Cloud Function to all departments at once, by including another parameter into its path:
exports.noticeAddedComp = functions.database.ref('main/notices/{dept}/{id}')
.onWrite( async (change, context) => {
console.log("Triggered for department: "+context.params.dept);
...
})
You can check if
evt.after.data().property;
is either null or doesn't exist, meaning it is deleted.
EDIT:
Scenario when you create something (document or field) :
Creation of the field would mean that the field in "before" doesn't exist at all, and in "after" it exists, but can be null.
Scenario when you update something
Field in the "before" is not the same as the one in "after".

What is the ValueEventListener equivalent in Firestore for FLUTTER app

Updated!!
Let's assume I have collection with name collection_name with document id signed_user_id . I am trying to listen the child node signed_user_id and perform one task only if that document signed_user_id changes. It is working but called twice when trying to listen the document changes.
_listenForHtmlContentUpdate() {
widget.firestore
.collection('collection_name')
.document('signed_user_id')
.snapshots()
.listen((event) {
print('object');
print(event.data);
});
And in this second case, let's assume I have inner collection and inner document with name html_doc. So, I want to listen when html_doc document field changes. This function is not working when I use multiple collection and document. I mean when trying to listen the inner html_doc document updates/changes. So, how to listen the inner document changes using flutter?
_listenForHtmlContentUpdate() {
widget.firestore
.collection('collection_name')
.document('signed_user_id')
.collection('html')
.document('html_doc')
.snapshots()
.listen((event) {
print('object');
print(event.data);
});
}
Firestore Update calling Twice Case: Acc to documentation, Local writes in your app will invoke snapshot listeners immediately. This is because of an important feature called "latency compensation." When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven't been written to the backend yet. You can use this property to determine the source of events received by your snapshot listener. This is the reason behind calling twice.
_listenForHtmlContentUpdate() {
print('_listenForHtmlContentUpdate *****');
widget.firestore
.collection('resume')
.document('signed_user_id')
.snapshots()
.listen((event) {
// check and perform the task accordingly.
var source = event.metadata.hasPendingWrites ? "Local" : "Server";
print(source);
});
The method snapshots() is equivalent to the ValueEventListener, both will listen for any changes in realtime. But in firestore you cannot listen for single field in a document. snapshot() will give realtime updates if any changes occur in the document.

Firestore document stream and update

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.

Is it possible to only retrieve the newest document with a QuerySnapshot in Firestore?

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.

Resources