When exactly is a read on firebase firestore performed - firebase

I'm reading a lot about "how to minify firebase read actions" and now trying to implement a function that caches the data and only updates modified one.
(Project is made with Dart and Flutter)
For this purpose, lets assume i have a subcollection containing 10 documents, each holding at least the field "lastModified".
here's my example:
GetOptions _CACHE = GetOptions(source: Source.cache);
GetOptions _SERVER = GetOptions(source: Source.server);
DateTime lastModified;
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference collectionReference = firestore.collection("collection").doc("document").collection("collection");
Query lastModifiedQuery = collectionReference.orderBy("lastModified", descending: true);
QuerySnapshot snapshot = await collectionReference.get(_CACHE);
if (snapshot.docs.isEmpty)
{
QuerySnapshot querySnapshot = await lastModifiedQuery.limit(1).get(_SERVER);
if (querySnapshot.size > 0)
{
lastModified = querySnapshot.docs.first.get("lastModified");
} else {
lastModified = DateTime.now();
}
} else {
Query queryRefresh = collectionReference.orderBy("lastModified", descending: true).where("lastModified", isGreaterThan: lastModified);
}
Now i am unsure about the different calls.
I know that i am firing a read operation on every document if i call ".get()".
But does just creating a query also runs a database operation and, in my case, updates the cache?
Or do i have to run a ".get()" on the query and i am done?

Creating a query does not access the database yet. Only once you call get() or listen to onSnapshot will data be read from Firestore.

Related

Update a field in the last document in Firestore collection Flutter

I am trying to update a field in the last document in the Firestore collection. My updating method is below:
updateHours() {
return usersRef.doc(firebaseAuth.currentUser!.uid).collection('posts')
.orderBy('datePublished', descending: true)
.limit(1).get().then((querySnapshot) {
return querySnapshot.docs.map((e) {
usersRef
.doc(firebaseAuth.currentUser!.uid).collection('posts')
.doc(e.reference.id)
.update({"totalTime": FieldValue.increment(1)});
});
});
}
This does not work. If I use .forEach(), then all documents get updated. So, how to update only the last document field?
To be able to update the totalTime field inside the last document, please use the following lines of code:
void updateHours() async{
CollectionReference postsRef = usersRef
.doc(firebaseAuth.currentUser!.uid)
.collection('posts');
QuerySnapshot query = await postsRef.orderBy('datePublished', descending: true)
.limit(1)
.getDocuments();
query.documents.forEach((doc) {
doc.reference.updateData({"totalTime": FieldValue.increment(1)});
});
}
Don't forget that Firebase APIs are asynchronous, and you need to wait for the data until it becomes available.

Why does persistenceEnabled: true result in my queries giving incorrect results?

I am using Firestore in my Flutter app. When I query a collection, I am getting the incorrect number of documents back.
The correct number of documents I should be getting back for my query is 20.
If I initialise Firebase as follows...
await Firebase.initializeApp();
FirebaseFirestore.instance.settings = Settings(persistenceEnabled: true);
I get only 2 documents back from my query.
If I initialize Firebase with peristenceEnabled false...
await Firebase.initializeApp();
FirebaseFirestore.instance.settings = Settings(persistenceEnabled: false);
I am wondering if it has to do with the fact I am only grabbing the first event in the stream. My query is as follows...
static Future<List<String>> myQuery(String personId , String bagId , String batchId , List<String> items) async {
var db = FirebaseFirestore.instance;
var q = db.collection('people')
.doc(personId)
.collection('bags')
.doc(bagId)
.collection('batches')
.where('batchId', isEqualTo: batchId)
.where('itemId', whereIn: items)
.where('status', isEqualTo: 'active');
var stream = q.snapshots().map((snapshot) {
List<String> results = [];
for (var doc in snapshot.docs) {
results.add(doc.id);
}
return results;
});
return stream.first;
}
}
If persistence is enabled this method returns a list of incorrect length. If persistence is disabled, this returns a list of the correct length.
I would expect the built in firestore caching mechanism would be smart enough to detect that any cached data is stale. I am therefore wondering if there is something wrong with my firestore data in general, such that it is breaking client side persistence/caching.
If you call snapshots() on a query, the Firestore SDK immediately invoked your callback with whatever data it has in the cache for that query (if any). It then checks with the server for any updates to the data, and (if any) invokes you callback again with the latest data.
But since you then call first() on the stream, you are only getting that first data from the local cache, and not the data from the server. If you only care about the current data, you should use get() instead of snapshots(), as that will first check for updates from the server before it invokes your callback.
So:
var snapshot = await q.get();
List<String> results = snapshot.map((doc) {
return doc.id;
});
return results;

get data from both collection and it is sub collection in firestore with flutter

i have this database structure in Firestore:
this what inside places
how can i make a query in flutter to get all data inside Places sub-collection along with name from User collection, this code to get all places sub-collection data from every User collection :
Future<List<PlaceModel>> getPlaces() async {
List<PlaceModel> placesList = [];
// get all docs from user collection
var users = await udb.get();
for( var uid in users.docs) {
var userData = await udb.doc(uid.id).get();
var userPlaces = await udb.doc(uid.id).collection(placeTable).get();
userPlaces.docs.forEach((place) {
placesList.add(PlaceModel.fromMap(place.data()));
});
}
return placesList;
}
You can also use where() or orderby() methods to get data with some rules.
Firestore.instance
.collection("users")
.document(uid.id)
.collection(placeTable)
.where('fieldName', isEqualTo:name )
.snapshots()
A single query can only access a single collection, or a group of collection that have the same name. Firestore queries are shallow and don't read data from subcollections. If you need the data from the subcollection, you will have to execute an additional query to read it.

How can I check the length of a firebase document using stream builder

In my flutter firebase app, I am able to get the length of a user's activity document in firebase using a query snapshot. However, I want the number of documents to update in real-time without the user needing to refresh the page. Can I do that by converting the codes below using stream builder to get the real-time length and how can I do that?
this is the code am using now which works perfectly well but doesn't update in real-time.
//this is the code I want to convert to stream
//builder.
static Future<int> numActivities(String userId)
async {
QuerySnapshot userActivitiesSnapshot = await
activitiesRef
.document(userId)
.collection('userActivities')
.where('seen', isEqualTo: '')
.getDocuments();
return userActivitiesSnapshot.documents.length;
}
You need to use the docs property, which "returns a List containing DocumentSnapshot classes", as follows:
return userActivitiesSnapshot.docs.length;
To get a stream of documents, you need to use the .snapshots() method which returns a Stream of QuerySnapshot instead of the .getDocuments() (deprecated in favor of .get()) method which returns a Future of QuerySnapshot.
Then you can map Stream<Snapshot> into a stream of the length of the snapshot's documents.
Your numActivities method should look like this:
static Stream<int> numActivities(String userId) {
return activitiesRef
.document(userId)
.collection('userActivities')
.where('seen', isEqualTo: '')
.snapshots()
.map((documentSnapshot) => documentSnapshot.docs.length);
}
Using this in your use case, you need to listen to the stream and update the _activityCount variable.
_setUpactivityCount() async {
final String currentUserId =
Provider.of<UserData>(context, listen: false).currentUserId;
DatabaseService.numActivities(currentUserId).listen((activityCount) {
if (mounted) {
setState(() {
_activityCount = activityCount;
});
}
});
}
Make sure you take care of _activityCount being null in it's initial state.

Firestore merge 2 queries with Flutter

I am trying to make 2 queries to Firestore and merge the results into one in order to simulate a Firestore OR query.
I segmented my code according to the bloc pattern.
///private method to zip QuerySnapshot streams
Stream<List<QuerySnapshot>> _combineStreams(String userId) {
var stream1 = todosCollection
.where("owners", arrayContains: userId)
.snapshots();
var stream2 = todosCollection
.where("contributors", arrayContains: userId)
.snapshots();
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
///exposed method to be consumed by repository
Stream<List<Todo>> todos(String userId) {
var controller = StreamController<List<Todo>>();
_combineStreams(userId).listen((snapshots) {
List<DocumentSnapshot> documents = List<DocumentSnapshot>();
snapshots.forEach((snapshot) {
documents.addAll(snapshot.documents);
});
final todos = documents.map((doc) {
return Todo.fromEntity(TodoEntity.fromSnapshot(doc));
}).toList();
controller.add(todos);
});
return controller.stream;
}
In my bloc I have the following code that should update my view accordingly my database state but it's not working. The database insertion work but the view doesn't refresh and I don't know why.
_gardensSubscription?.cancel();
_gardensSubscription = _dataRepository.gardens(event.userId).listen(
(gardens) {
dispatch(
GardensUpdated(gardens),
);
},
);
I am not very confortable with Stream and particularly with StreamController process. Is it possible to do this task more easily?
Time to use the great powers of RxDart: https://pub.dev/packages/rxdart
You can't do all types of streams transformations with this lib.
For example, you can use the merge operators to achieve exactly what you want

Resources