What is the difference between .valueChanges() and .get() - firebase

I am wonder what is the difference between .valueChanges() and .get()
Here is the signatures:
valueChanges(): Observable<T[]>;
get(options?: firestore.GetOptions): Observable<firestore.QuerySnapshot>;
If you take a look at this tow calls they returns the same result:
this.firestore.collection('version').valueChanges().subscribe(x => {
console.log;
});
this.firestore.collection('version').get().subscribe(x => {
console.log;
});
It is seems like in case of .get() you can play with GetOptions: 'server' | 'cache' is there other benefits?
In my particular use case I just want to take the data from the server and disconnect, I want to minimize the number of connections to firebase as much as possible.

get() just fetches data a single time.
valueChanges() allows your code to observe changes that happen to documents over time.
Choose the one that meets the needs of your app. If you don't need to be updated with changes to documents as they happen, then don't use valueChanges().
Neither of these establishes any "connections". All Firestore operations are pipelined over a single connection maintained by the SDK.

get() is a method to fetch data
valueChanges() is a multicasting observable that emits an event every time the value of the control changes, in the UI or programmatically
Difference:
get() can get data only once, whereas valueChanges() is automatically fired whenever something changes and retrieve data (observer) by subscribe()

Related

What is the difference between getDocuments() and snapshots() in Firestore?

I am a little confused about the difference between these two. My understanding is that getDocuments is a type of Future and seems to get the entire documents according to the query. while snapshots, on the other hand, is a type of Stream and, correct me if I'm wrong, I think it represents the results of the query? I need a more specific explanation of this issue. I will include some code snippets as an example for more clarification
getDocuments()
getUserById(String userId) async {
return await _firestore.collection("users").where("userId", isEqualTo: userId).getDocuments();
}
snapshots()
getUserById(String userId) async {
return await _firestore.collection("users").where("userId", isEqualTo: userId).snapshots();
}
So what's the difference?
When you call getDocuments(), the Firestore client gets the documents matching the query from the server once. Since this may take some time it returns a Future<QuerySnapshot>.
When you call snapshots() the Firestore client gets the documents, and then keeps watching the database on the server for changes that affect your query. So if document is written in the users collection that affects your query, your code gets called again. So this returns a stream of QuerySnapshot.
In both cases the results for the entire query are in the QuerySnapshot object.
I highly recommend reading the Firestore documentation on getting data once and on listening realtime updates. While they don't contain Flutter examples, the explanation in there applies equally to the Flutter libraries.
getDocuments():
It's used to provide data once. Cloud Firestore contains collections and inside these collections, you have documents that may contain subcollections or fields mapped to a value. To retrieve any of the doc fields to used it in widget this is used.
snapshots():
It will be called on every data change in your document query. For this StreamBuilder must be used to fetch fields as modified.
In short, it will do the job of setState() where it gives you the response for every modification so that UI can be updated.

Why does Firestore send all results, instead of just the new items when using on_snapshot?

I'm using Firebase's Firestore to store and publish new events.
In the code below, I'm subscribing to a collection and want to be notified when a new items is added (this code is executing on a browser).
When I first connect, I would like to receive a true snapshot. However, once I'm connected to Firestore and have received an initial snapshot, with each new item, I only want to get the udpates, not the whole collection over and over again!
function queryExercise(exercise){
db.collection("exercises").where("exercise","==",exercise).onSnapshot(function(querySnapshot){
querySnapshot.forEach(function(doc){
var d_ = doc.data()
console.log(d_);
...do somethign with d_...
})
})
}
When I publish a new item to the collection, my console is full of all events received earlier...in other words, it is sending me the full snapshot, instead of just the deltas.
Am I doing something wrong or does the API really not support delta updates?
Looks like I needed to read on docChanges:
function queryExercise(exercise){
db.collection("exercises").where("exercise","==",exercise).onSnapshot(function(querySnapshot){
// \/-----this thing
querySnapshot.docChanges().forEach(function(change){
var d_ = change.doc.data()
console.log("Change type:", change.type, d_);
...
});
})
}
From https://firebase.google.com/docs/firestore/query-data/listen
That's the way Firestore queries work. If you don't provide a filter for which documents you want in a collection, you will get all the documents in that collection. The only way to change this behavior is to provide a filter in your query using a where clause.
It sounds like you have a thought in mind about what makes for a "new" document in your collection. You will need to represent that using some field in the documents in your collection. Usually this will be a timestamp type field that's added or modified whenever a document is created or changed. This will be part of your where clause that determines what's "new". Use this field as a filter to find out what's new.

How to update the same document with a read from the same collection in an onUpdate function

I'm trying to update the same document which triggered an onUpdate cloud function, with a read value from the same collection.
This is in a kind of chat app made in Flutter, where the previous response to an inquiry is replicated to the document now being updated, for easier showing in the app.
The code does work, however when a user quickly responds to two separate inquiries, they both read the same latest response thus setting the same previousResponse. This must be down to the asynchronous nature of flutter and/or the cloud function, but I can't figure out where to await or if there's a better way to make the function, so it is never triggering the onUpdate for the same user, until a previous trigger is finished.
Last part also sound a bit like a bad idea.
So far I tried sticking the read/update in a transaction, however that only seems to work for the single function call, and not when they're asynchronous.
Also figured I could fix it, by reading the previous response in a transaction on the client, however firebase doesn't allow reading from a collection in a transaction, when not using the server API.
async function setPreviousResponseToInquiry(
senderUid: string,
recipientUid: string,
inquiryId: string) {
return admin.firestore().collection('/inquiries')
.where('recipientUid', '==', recipientUid)
.where('senderUid', '==', senderUid)
.where('responded', '==', true)
.orderBy('modified', 'desc')
.limit(2)
.get().then(snapshot => {
if (!snapshot.empty &&
snapshot.docs.length >= 2) {
return admin.firestore()
.doc(`/inquiries/${inquiryId}`)
.get().then(snap => {
return snap.ref.update({
previousResponse: snapshot.docs[1].data().response
})
})
}
})
}
I see three possible solutions:
Use a transaction on the server, which ensures that the update you write must be based on the version of the data you read. If the value you write depends on the data that trigger the Cloud Function, you may need to re-read that data as part of the transaction.
Don't use Cloud Functions, but run all updates from the client. This allows you to use transactions to prevent the race condition.
If it's no possible to use a transaction, you may have to include a custom version number in both the upstream data (the data that triggers the write), and the fanned out data that you're updating. You can then use security rules to ensure that the downstream data can only be written if its version matches the current upstream data.
I'd consider/try them in the above order, as they gradually get more involved.

Mismatch in count even with FieldValue.increment

My use case is that I want to keep aggregating my firebase user count in the database for quick and easy access. For that, I have a cloud function listening on user.onCreate and it simply increments a field in a document using the atomic FieldValue.increment.
Here is the code:
exports.createProfile = functions.auth.user().onCreate(async user => {
return Promise.all([
addProfileToDatabase(),
function() {
db.collection('someCollection').doc(docId).update({
count: admin.firestore.FieldValue.increment(1)
)}
}
])
})
Issue: the count in the database becomes more than the number of authenticated users shown in the Authentication tab of Firebase. I regularly reset it to the correct number and then it slowly increases again.
I have read about the write throttling on a document, but that should instead result in lower count if at all. But why is that the count in the database always overshoots the actual count?
Without seeing your code, the only thing I can imagine is that your function isn't idempotent. It's possible that functions may be invoked more than once per triggering event. This would be an explanation why your count exceeds the expectation.
Read more about Cloud Functions idempotency in the documentation and also this video.

Is it good idea to use admin.database().ref().on('child_added') in cloud functions?

I'm creating a general purpose queue on firebase cloud functions to run huge list of task. I was wondering if i can use .on('child_added') to get new task pushed to the queue.
Problem i was getting is that my queue is breaking in a middle randomly after 10 mins or sometimes 15 mins.
admin.database().ref('firebase/queues/').on('child_added', async snap => {
let data = snap.val();
console.log(data);
try {
await queueService.start(data);
} catch (e) {
console.log(e.message);
}
snap.ref.remove();
});
Or shall i go back to use triggers?
functions.database.ref('firebase/queues/{queueId}').onCreate(event => {
return firebaseQueueTrigger(event);
});
You can use child_added in cloud functions if you just want to retrieve data from the database.
onCreate() is a database trigger, that is triggered whenever new data is added to the database.
more info here:
https://firebase.google.com/docs/functions/database-events
so when new data is added to the database, at the location provided onCreate() will trigger. Both can also be used in cloud functions
The problem is not in using child_added, it is in keeping an active on(... in Cloud Functions. The on(... method attaches a listener, which stays attached until you call off(). This by nature conflicts with the ephemeral nature of Cloud Functions, which are meant to have a trigger-execute-exit flow. Typically if you need to read additional data from the database in your Cloud Function, you'll want to use once(.... so that you can detect when the read is done.
But in your specific case: if you're creating a worker queue, then I'd expect all data to come in with event.data already. Your functions.database.ref('firebase/queues/{queueId}').onCreate(event is essentially the Cloud Functions equivalent of firebase.database().ref('firebase/queues').on('child_added'.

Resources