Snapshot loading random data from cache before getting from the server - flutter

I have a streambuilder where I'm loading products
StreamBuilder<QuerySnapshot>(
stream: _ref.collection("products").where("category", isEqualTo:
widget.selectedCategory.nameLabel).orderby("standing",descending:false).limit(10)
builder: (context, snapshot) {
if(snapshot.hasData){
if(snapshot.data!.docs.isNotEmpty) {
for (var changes in snapshot.data!.docChanges) {
//adding data to list<Products> products for pagination
}
}
return ProductsList.vertical(
scrollController: _scrollProducts,
products: products,
scroll: true,
);
}
The problem is I'm receiving more than 10 documents. If there is cache data, it will load that first then load the actual data ordered by 'standing'.
This problem exists mostly on the first load when cache data is not correct. How can we fix this?

What you're describing sounds like the expected behavior. The Firebase SDK for Firestore will:
immediately give you the documents from the local cache that meet the query conditions,
then reach out to the server to get latest content, with which it then updates its local cache.
finally fire an additional event on the stream with this latest content.
I don't think there's a way to bypass the first event on your stream in Flutter, which you can detect that snapshots come from the cache (and may not be up to date with the server) by checking their document.metadata.isFromCache property.

Related

How the Firebase Firestore's snapshots() is made and how it can listen to the database updates with a stream inside the flutter app

in the snapshots() method that returns a Stream of document or collection snapshots from Firestore's database:
FirebaseFirestore.instance.collection("collection").snapshots();
How the stream is done, I mean what should I know and learn in order to make as an example a Stream that will listen to endpoint/database changes?
I have an idea about using web sockets but I don't think this is what it's used in the snapshots().
and I don't want some way to create a Stream that requests new data every n Duration.
I want something that does nothing when nothing happens in the backend, but once we change something the Stream should know about it and listen to it.
Thank you!
You have the first part right already. Store it in a variable.
final myStream = FirebaseFirestore.instance.collection("collection").snapshots();
Next, you need to use a StreamBuilder pass in your stream
StreamBuilder(
stream: myStream,
builder: (context, AsyncSnapshot response) {
if(response.connectionState==Connectionstate.waiting) return const CircularProgressIndicator();
final data = response.data();
return WidgetToDisplayData()
}
),
Pass all the other required arguments including the builder widget, which refers to what you want. Firebase has a caching mechanism that ensured a listener is only active if there are changes detected in the db

Firebase Firestore reads on streams

I am using Firestore stream in my flutter app. The data is user profiles so I need to filter it as needed. My initial data stream is as follows
Stream s = FirebaseFirestore.instance
.collection('users')
.snapshots(includeMetadataChanges: true);
and when I have to filter data, I do
setState((){
s = FirebaseFirestore.instance
.collection('users')
.where('name',isEqualTo:"MyName")
.snapshots(includeMetadataChanges: true);
});
This works fine but my concern is reads, I know that data is cached and is read from server only if something changes but I don't know of streams.
I tried monitoring the usage on firebase but due to the app being in production I couldn't measure anything properly.
And if filtering by changing stream does cause reads on firebase then what else can I do to filter data from cache.
One thing I thought off was storing everything locally but storing data of thousands of users locally is impractical.
A stream is a sequence of asynchronous events. It is like an
asynchronous Iterable—where, instead of getting the next event when
you ask for it, the stream tells you that there is an event when it is
ready.
From Dart (https://dart.dev/tutorials/language/streams)
A stream is a continues datastream that closes when the class is terminated or by canceling the stream object:
streamSub.cancel();
You can use a stream in a streambuilder. Every time a change is applied to any of the documents, it rebuilds the builder.
StreamBuilder(
stream: stream,
builder: (BuildContext content, AsyncSnapshot snapshot) {
}
)
If 1 document changes in query results, you will be billed 1 read. You are not billed for documents that are unchanged. The query snapshot object merely reuses the document data that was in memory from the prior set of results. By Doug Stevenson (https://stackoverflow.com/a/60062304/9142870)

Sometimes my Cloud Function returns old data from Firestore. Is it a cache problem?

Client-side, I'm using a listener to detect if the "notifications" collection of the user changes. The App calls a Cloud Function that retrieves the last three unread notifications and the total number of unread notifications.
In my App, I have this:
Listener
firestore.collection("users")
.doc(uid)
.collection("notifications")
.snapshots().listen((QuerySnapshot querySnapshot) {
NotificationsPreviewModel notificationsPreview =
await _cloudFunctionsService.getNotificationsPreview(doctor.id)
})
Cloud Function
exports.getNotificationsPreview = functions.https.onCall(async (data, context) => {
const userId = data.userId;
let notifications = [];
const notificationsDocuments = await db
.collection("users")
.doc(userId)
.collection("notifications")
.orderBy("dateTime", "desc")
.get();
notifications = notificationsDocuments.docs.map((rawNotification) =>
rawNotification.data()).filter((element) => element.unread == true);
const notificationsNumber = notifications.length;
notifications = notifications.slice(0, 3);
return { "notifications": notifications, "notificationsNumber": notificationsNumber };
});
The Cloud Function gets called only when a change is detected, so it shouldn't return old data.
The error appears only the first time the Cloud Function is called from the App's start, but not always. The following calls don't generate the error.
How can I solve this? For now, I've added a delay of 500ms, and it works perfectly, but it's not a real solution.
Based on your description, it sounds like you see some form of latency while collecting the data from Firestore. Retrieving data from the Cloud takes time, and a delay of 500ms is not excessive.
I am not familiar with Flutter enough to comment on your code. However, according to the documentation for Java:
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
Source:
By providing a Source value, these methods can be configured to fetch results only from the server, only from the local cache, or attempt to fetch results from the server and fall back to the cache (which is the default).
If you are online, get() checks the server for the latest data, which can take between 300ms and 1500ms depending on several factors. For example, where is your Firestore instance located in comparison to your Cloud Function and client? Try adjusting the delay and see if you can identify the timing.
There are also some soft limits you should be aware of as this might also impact your timings for how quickly you can retrieve the data. There is a maximum sustained write rate to a document of 1 per second. Sustaining a write rate above once per second increases latency and causes contention errors.
As for the documentation:
When you set a listener, Cloud Firestore sends your listener an initial snapshot of the data, and then another snapshot each time the document changes.
It seems that you are initially receiving the snapshot of the data, and then the following updates, as expected.
You can check some possible solutions to this in this post.

Updating a StreamBuilder with new data in Flutter and Firestore

I am using a StreamBuilder in Flutter to get realtime updates of a firestore collection users. I want to filter the users by age.
StreamBuilder<List<User>>(
stream: UserList(minAge, maxAge),
builder: (context, snapshot) {
print(snapshot.data);
}
);
Basically the underlying firestore query is just a simple where query.
FirebaseFirestore.instance
.collection('users')
.where('age', isGreaterThan: minAge)
.where('age', isLessThan: maxAge)
.snapshots();
Every time the user changes the values of minAge or maxAge in the UI, the StreamBuilder receives those new parameters and gets updated. Does this cause a new query every time? If yes, will this query fetch all objects from the database every time or will that fetch only new objects and the rest from my local cache?
Yes, your code will make a new query every time minAge or maxAge changes. The query cannot be updated with dynamically changing filters. The query will not use the cache unless the app is offline, or you direct the query at the cache only (which is not a good idea unless you know that all possible documents are already cached).

Slight delay when receiving data from Firestore - any way to speed up or smooth?

I have a flutter app which uses Firestore as a database. When a user has no data a page is displayed with a message
'Add some products'
If the user has data they see a list of their products.
The problem I have is when I click out of this tab and return to it, for a fraction of a second (approx. 0.3 seconds) the message saying 'Add some products' is displayed, then it retrieves the data and displays the list of products.
I am looking for a way to either remove this delay, or just make the transition smoother.
You can't realistically remove the delay of a Firestore query. It's always going to take some unknown amount of time to query the database, the amount of time mostly depending on the quality of the network connection between the client and the server. If you're depending on cached results, it will still take time to load documents from disk.
Your app should initially display some sort of loading indicator (or even nothing at all) until the query generates data to act on. If you want to be able to return to the screen without any delay, you're going to have to cache the results of the query in memory for reuse.
I don't know a way to speed up... because the query data return depends on a lot of factor including internet speed. So other users may get an increased than only 0.3 sec. It's better to use a FutureBuilder for futures.
eg :
like this;
FutureBuilder(
future: Firestore.instance.collection('collectionName').document(docId).get(),
builder: (BuildContext context, DocumentSnapshot snapshot){
if(!snapshot.exits){
return Text('Add items'); //yourWidget to add items
}if else (snapshot.exits){
return YourWidget(); //your widget which shows the items
}else{
return CircularProgressIndicator();
}
}
)