How to retrieve 1 document from firebase/firestore collection based on date in dart/flutter? - firebase

Ofc, I know the basic way we retrieve a whole bunch of documents from a collection like so:
stream: Firestore.instance
.collection("collection_name")
.orderBy("date", descending: true) // new entries first
.snapshots(),
builder: (context, snapshot) { ....
This is fine when I'm displaying a whole bunch of data.
But I have situation where I need to do some calculations with the last added numbers, meaning I just need to get 1 document, so I what I do is sort data by date and limit by 1, then make the query for this one document like this
List<DocumentSnapshot> list;
QuerySnapshot querySnapshots;
if (await AuthHelper.checkIfUserIsLoggedIn()) {
String userId = await AuthHelper.getUserID();
querySnapshots = await Firestore.instance
.collection("collection_name")
.orderBy("date", descending: true) // new entries first, date is one the entries btw
.limit(1)
.getDocuments(); // Get Documents not as a stream
list = querySnapshots.documents; // Make snapshots into a list
return (list ?? null);
} else {
return null;
}
But is this the best way to do it ? When making this query aren't I getting the whole set of documents and discarding the rest ? Which means when this collections grows this is going get slower and slower ?
Is there a better way to retrieve the last added document ? So I can use it as list/array/map and not as stream ?
Thanks.

When you limit the query, you are only ever going to read that number of documents maximum. It's not going to read the entire collection and discard the extras.

Related

Firebase Firestore Getting documents again on query stream

FirebaseFirestore.instance
.collection(
'chats/${site}/conversations/${room.id}/messages')
.orderBy('createdAt', descending: true)
.where("createdAt", isGreaterThan: dateTime )
.snapshots()
.map(
(snapshot) {
So, On the first document that inserted to the firestore, the I get i a snapshot. On the second, the stream return the first and the second,
So the i get -
(Doc A)
(Doc A,Doc B)
(Doc A, Doc B, Doc C)
And so on. Is there a way to get:
(Doc A)
(Doc B)
(Doc C)
?
I reviewed your snippet and it appears you are using a Stream from the snapshot() method of a CollectionReference type. According to the documentation, this will stream events as they happen in Firestore. You mentioned that with each document inserted in Firestore, you also started getting the previous documents that were inserted before, instead of getting only the one which was just inserted (the latest). This might be related to the dateTime variable you are using to filter documents. Since you are using a greater than comparison, any documents created after the time set in the dateTime will be returned from the query. This could explain why your query returns additional documents each time a new one is added with a timestamp after the dateTime variable.
If you would like to get only the latest document added to the database each time, you can make use of a query limiter. I tested the limitToLast method to get only the latest document added and it appears to work in my testing. This method returns the very last document in a query, and in order for this to be the newest you would have to invert the process to order by ascending (oldest first) so that the newest document is at the bottom:
FirebaseFirestore firebase = FirebaseFirestore.instance;
firebase
.collection('users')
.orderBy('createdAt', descending: false) // orders by ascending order, latest document is the last
.limitToLast(1) // gets the last document, you can set how many docs to get
.get()
.then((QuerySnapshot snapshot) {
if (snapshot != null) {
// Data is available
snapshot.docs.forEach((doc) {
print(doc['firstName']);
});
} else {
print("No data found");
}
}
for everyone who reach this issue on 2022, the solution is rather simple.
You can stay with the same query but check the doc changes:
snapshot.docChanges.forEach((docChange) {
final data = docChange.doc.data() as Map;
LimitToLast won't solve your problem if the internet connection was down for a few moments and multiple updates arrived, but docChanges is all the changes since the last snapshot.
Note: You need to ignore the first time because it will return all the docs on the collection at the first time.

How to get Firestore data from documents of different collections

I'm trying to collect data from different documents of different collections, and doing it in a for loop increase the code processing time
for (var i = 0; i < productDataList.length; i++) {
// Get full data according to shared preferences
var firestore = FirebaseFirestore.instance;
DocumentSnapshot ds1 = await firestore
.collection('products')
.doc(productDataList[i][4])
.collection(productDataList[i][4])
.doc(productDataList[i][0])
.get();
// Add product DocumentSnapshot to map
productFullDetails.add({'productDoc': ds1});
}
Does anyone know a better method to do this?
Thank you.
You can move the asynchronous operation out of the for-loop by creating a list of the async operations Futures and getting all the results at once by using Future.wait.
var firestore = FirebaseFirestore.instance;
final List<Future<DocumentSnapshot>> documentSnapshotFutureList =
productDataList
.map((productData) => firestore
.collection('products')
.doc(productDataList[i][4])
.collection(productDataList[i][4])
.doc(productDataList[i][0])
.get())
.toList();
final List<DocumentSnapshot> documentSnapshotList = await Future.wait(documentSnapshotFutureList);
productFullDetails = documentSnapshotList
.map((DocumentSnapshot documentSnapshot) =>
{'productDoc': documentSnapshot})
.toList();
Note the following with using Future.wait,
Returns a future which will complete once all the provided futures
have completed, either with their results, or with an error if any of
the provided futures fail.
The value of the returned future will be a list of all the values that
were produced in the order that the futures are provided by iterating
futures
https://api.flutter.dev/flutter/dart-async/Future/wait.html
The problem is not caused by the for loop per se, look into what is happening in the for loop. If you making an asynchronous network request and awaiting for it. If you have a list of products that is 100 items long, you have to make 100 requests, and wait for each one to finish. If every request was 200 milliseconds[which is not slow], that's 20 seconds.
I would probably skip doing it in a for loop, and use a stream builder to get every single document in a stream by it's self, which is technically the same amount of reads.
Use your for loop to create a steam, pseudo code will look like this:
Column(
children:[
for (var product in productDataList)
StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('products')
.doc(product[4])
.collection(product[4])
.doc(product[0])
.snapshots(),
builder: (context, snapshot){
if(snapshot.hasData) productFullDetails.add({'productDoc': snapshot.data() as Map<String,dynamic>});
return Text('something');
}
)
])
This will get your data simultaneously, without having to await for them to finish after one another.

Flutter Firebase Pagination - Nested Query

I am relatively new to the Flutter world and need some help in efficiently querying a nested Firebase query with pagination in Flutter.
I am building an app where I have a few thousand of user selected documents in collectionA (selected from >100k documents from collectionB) and need to loop through a chunk of 10 documents (whereIn limit of 10) queried via stream. My current implementation (snippet below) takes a list of document ID's "d" and chunks in a list of 10 via quiver/iterables. The drawback of the methodology is that it reads thousands of user documents upfront before displaying it in the app. I would like to use a pagination and control the reads.
Would you suggest a solution (with a code snippet) on how to use the pagination through thousands of user selected documents?
Future<List<Article>> fbWhereGT10Article(
String collection, String field, List<dynamic> d) async {
final chunks = partition(d, 10);
// print(chunks);
final querySnapshots = await Future.wait(chunks.map(
(chunk) {
Query itemsQuery = FirebaseFirestore.instance
.collection(collection)
.where(field, whereIn: chunk);
// .orderBy('timestamp', descending: true);
return itemsQuery.get();
},
).toList());
return querySnapshots == null
? []
: await Stream.fromIterable(querySnapshots)
.flatMap((qs) =>
Stream.fromIterable(qs.docs).map((e) => Article.fromFirestore(e)))
.toList();
}

How to filter Firebase Documents that are in a list of DocumentReferences?

I'm currently building a social network app using Firebase and Flutter, and I've stumbled onto a bit of a problem. My homepage has two tabs, one that contains all posts on the app in chronological order, and the other that contains the posts of the people you follow. The DocReferences of the people the user follows is inside a list. Currently, the code looks like this:
if (myFollows.isNotEmpty)
for (int j = 0; j < myFollows.length; j++) {
await FirebaseFirestore.instance
.collection('posts')
.orderBy('date', descending: true)
.where('user', isEqualTo: myFollows[j])
.get()
.then((value) {
//code
});
But, as you can see, I create seperate queries for each of the followed users, so the resulted list of posts isn't in chronological order.
So, my question is, if there is a way I could query the post documents where the user variable is contained inside the list myFollows, instead of comparing it to each of its values one by one?
Remove the loop and use whereIn. This should work
if (myFollows.isNotEmpty)
await FirebaseFirestore.instance
.collection('posts')
.orderBy('date', descending: true)
.where('user', whereIn: myFollows) //this line changed
.get()
.then((value) {
//code
);
In your 1st execution, you may need to add a new index... just follow the web link (in error message) that will help create this required index.
May not work if following more than 10 users as this is a built-in limit in Firestore (maximum 10 comparisons).
In that case, there is no built-in solution... you need to keep your loop and append every single query separately... then sort your final list.

How to check if an array in a firestore document is empty?

I'm building out an app in flutter and I want to run a query to determine if the array in my firestore document is empty. Is there a way to do this?
Here is a picture of my data structured in firestore.
I know there is an arrayContains method but I'm not sure on how to check for an empty array. Here is my current code.
_postsStream = await _firestore
.collectionGroup('posts')
.where('timestamp', isGreaterThanOrEqualTo: _start)
.where('timestamp', isLessThanOrEqualTo: _end)
.where('problems', arrayContains: )
.snapshots();
I left the arrayContains: intentionally empty for now. Please advise on how I would go about implementing this feature. Thanks in advance!
An empty array defaults to [] So this is possible by doing the following:
Do not use ' ' for the square brackets
final _database = Firestore.instance;
return _database
.collection('arrays')
.document(arrId)
.where('array', isEqualTo: [])
// .where('array', isNull: true) can be used to check if it's null
.snapshots()
.map(_itemListFromSnapshot);
You cannot do it in firestore currently.
What you can do is.
Create a separate property hasProblems which defaults to false.
If the user try to register a problem, then check the flag hasProblems.
If hasProblems==false, then toggle it to true and add the problem to the list.
else no need to toggle, just add the new problem to the list.
If problems are to be removed later on, then check for the list to get empty.
Once it is empty, then toggle hasProblems back to false.
This way you can achieve what you want-
postsStream = await _firestore
.collectionGroup('posts')
.where('timestamp', isGreaterThanOrEqualTo: _start)
.where('timestamp', isLessThanOrEqualTo: _end)
.where('hasProblems',isEqualsTo: false)
.snapshots();
Maybe you can have other better solutions, but this is the one that came in my mind.
You can follow the solution #mubin986 has given, but once the list gets bigger and bigger, it impacts performance.
Cheers, hope it helps.
There is no way to check with firestore query if an array is empty or not.
But, you can follow these steps:
Query the data with range filters:
_postsStream = await _firestore.collectionGroup('posts')
.where('timestamp', isGreaterThanOrEqualTo: _start)
.where('timestamp', isLessThanOrEqualTo: _end)
.snapshots();
After fetching the data, iterate over all the DocumentSnapshot and check if problems array is empty or not.

Resources