I use a Firebase Realtime Database StreamSubscription to listen to events including onChildAdded and onChildChanged.
For example this will fetch all existing entries at dbRef and any future additions while the stream is active.
dbRef.onChildAdded.listen((event) async {
String? key = event.snapshot.key;
Map value = event.snapshot.value as Map;
// Do stuff with object...
});
However, as onChildAdded fetches ALL data and then subsequent additions, I don't know when the initial/existing data is finished fetching.
I could use dbRef.once() which would fetch the initial/existing data and I would know when that is complete, allowing me to present a loading UI. However, using onChildAdded after this would leave me fetching data twice.
How can I fetch the initial/existing data, know when that is done, so that a loading UI can be displayed appropriately and then listen to future additions?
firebaser here
If you listen to both child and value events in the Realtime Database, the value event for an update will only fire after all child events for that same have fired.
Knowing this, a common trick is to listen to both onChild... and onValue events (or the once future). You can then build/update the state in the child handlers, and commit the new state in the value handler.
The SDK automatically deduplicates listeners on the same query/reference, so having multiple listeners on the same query/ref will only read the data from the server once.
Related
I am using the firebase-js-sdk and the stateChanges function to detect incoming changes.
In my database I store messages. After subscribing the first time to stateChanges the observer is fired for each message, and at the moment I reload the UI after each update. Is there a way to prevent unnecessary UI updates?
this.firebaseMessagesObservable$.subscribe((message: any) => {
this.update(message);
this.refreshUi();
});
In the flutter firestore codebase you can find a comment about the stream it creates when you run snapshots() on a query.
// It's fine to let the StreamController be garbage collected once all the
// subscribers have cancelled; this analyzer warning is safe to ignore.
StreamController<QuerySnapshotPlatform> controller; // ignore: close_sinks
I want to wrap my resulting snapshot streams with a BehaviorSubject so I can keep track of the latest entry. This is useful when I have one stream that is at the top of a page that I want to be consumed through different widgets farther down in my tree without reloading the stream each time. Without keeping track in a BehaviorSubject or elsewhere if a new widget starts listening to that stream it does not get the most recent information from Firestore as it missed that event.
Can I also not worry about closing the behavior subject I am going to create as it will be garbage collected when there are no more listeners? Or is there another way to achieve what I am wanting?
I'm picturing code like this:
final snapshotStream = _firestore.collection('users').snapshots();
final behaviorSubjectStream = BehaviorSubject();
behaviorSubjectStream.addStream(snapshotStream);
return behaviorSubjectStream;
This will get a complaint that I don't close the behaviorSubjectStream. Is it ok to ignore?
That depends on how you listen to the subject.
From what you describe, it sounds safe to ignore the hint. When the subscriptions that listen to the subject are cancelled, the subject will be cancelled as well (when the garbage collector finds it).
There are situations where you have a subscription that is still listening, but you want the subject to stop emitting. In that case you will need to close() the subject.
You can test that the subject is correctly cancelled by adding
behaviorSubjectStream.onCancel = () {
print("onCancel");
};
Then you can test it by playing around with your app.
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()
I am new to firebase and noSQL databases. I've read the docs and watched instructional videos but there is one concept I haven't fully grasped.
Why is it that querying the database requires a listener such as .on("value")?
Since these listeners are triggered whenever there is a change of sorts (nodes created, edited, children created) shouldn't there be a more direct way of getting the data from the db? Such as
ref.orderBy("age"). equalTo(30).get()
A method to just get what's in there at the time he instruction is executed, without having to listen to some sort of event?
In SQL it's not like you have to wait for something to change in your db to make this query work:
SELECT * FROM TABLE WHERE X == Y
PS: I know .once() also exists, but my question is more about: if my db never changed, how would I be able to query it and always get the same query result/snapshot?
You didn't define a platform so I will use this Swift pseudo-code example. Suppose we want to get a list of all users, one time.
let usersRef = my_firebase.child("users")
usersRef.observeSingleEvent(by: .value, with: { snapshot in
//all users are returned to the app in the snapshot
// .value means 'give me everything in the specified node'
// so then we can iterate over the snapshot to get each users data
}
You can call the above code any time to get the list of users and it does not add an observer (listener) to the node. It's a one-shot.
Whereas, if we want to be notified of users that are added
let usersRef = my_firebase.child("users")
usersRef.observe(by: .childAdded, with: { snapshot in
//upon first calling this, each user will be sent to the app in the snapshot
// and after that, any user that's added
}
The above code attaches an observer (listener) to the users node and whenever a user is added, it's provided to the app via the snapshot.
Note the use of .observeSingleEvent vs .observe and .value to get everything in the node vs .childAdded to get a specific node.
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'.