Error using orderBy and startAfterDocument in a query - firebase

When using orderBy and startAfterDocument in the same query, I get the following error:
failed: Status{code=FAILED_PRECONDITION, description=The query requires an index. You can create it here: https://console.firebase.goog...
This error is being caught when I try to receive the next set of documents.
Tried to create an index using the link provided in the error but it's a single index using the 'finalTimeStamp' field is being generated and firestore throws out the following exception.
this index is not necessary, configure using single field index controls
In my single field indexes, the descending index for a collection scope is already enabled.
My code:
if (_lastVisible == null) {
try {
data = await chatList
.document(widget.currentUserId)
.collection('inbox')
.orderBy('finalTimeStamp', descending: true)
.limit(10)
.getDocuments();
} catch (e) {
print('caught error 1');
}
} else {
try {
data = await chatList
.document(widget.currentUserId)
.collection('inbox')
.orderBy('finalTimeStamp', descending: true)
.startAfterDocument(_lastVisible)
.limit(10)
.getDocuments();
} catch (e) {
print('caught error 2');
}
}

The orderBy and startAfter or startAfterDocuments should be performed on the same field.
In my question above I've performed orderBy on the finalTimeStamp field and was using startAfterDocument which considers the document ID while ordering the documents. Hence, firestore throws an error.
You can solve this issue in 2 ways:
You can perform startAfter() on finalTimeStamp field. (Didn't consider this for my case because I though there may be multiple documents with the same finalTimeStamp. Using this could give out wrong results)
I created a chatRef field in my documents referencing the same document. I then created an index in firestore to order my documents by finalTimeStamp first and chatRef later. Now while querying, I performed orderBy() twice on finalTimeStamp and chatRef along with the startAfterDocument().

Related

Does QuerySnapshot only returns added/updated/removed documents or all documents from Firestore in kotlin?

Does QuerySnapshot only return added/updated/removed documents or all documents, not only updated ones, from Firestore?
If it returns all the documents, then is there any way to get only newly added/updated/removed documents?
What is the difference between getDocuments() and getDocumentChanges() in Firestore's QuerySnapshot and when to use them?
In the below code, is it returning all documents or only added/modified/removed documents? Because It's like we are getting all documents and then sorting them according to their state. Is it correct?
.addSnapshotListener { snapshots, e ->
if (e != null) {
Log.w(TAG, "listen:error", e)
return#addSnapshotListener
}
for (dc in snapshots!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> Log.d(TAG, "New city:${dc.document.data}")
DocumentChange.Type.MODIFIED -> Log.d(TAG, "Modified city: ${dc.document.data}")
DocumentChange.Type.REMOVED -> Log.d(TAG, "Removed city: ${dc.document.data}")
}
}
}
Edit:
Here is my code
.addSnapshotListener { value, error ->
if (error != null) {
cancel(
message = "Error fetching posts",
cause = error
)
return#addSnapshotListener
}
if (value != null) {
Log.d(TAG, "main value: ${value.size()}")
for (dc in value.documents) {
Log.d(TAG, "dc ${dc.data}")
}
offer(reaction)
}
}
Initially, when the app is open I am getting all documents and it's ok. But when I am modifying one document still I am getting all documents(Log.d(TAG, "main value: ${value.size()}")answer is 2. I have a total of 2 documents right now so I am getting both documents modified and not modified). Means first I will get All documents and then I will sort them by using getDocumentChanges().
My code is in Kotlin.
Does QuerySnapshot only return added/updated/removed documents or all documents(not only updated ones) from Firestore?
A QuerySnapshot object contains all the results of a Firestore query. So if you perform a query in Firestore, all the results that are returned can be found in the QuerySnapshot object.
If it returns all the documents then is there any way to get only newly added/updated/removed documents?
Yes, there is. You use an addSnapshotListener(EventListener listener) to listen for updates in real-time. This means that can always view changes between snapshots. So you can be notified if a document is only added, modified, or removed. But please also note, that there is no way you can skip the initial retrieval of the documents.
What is the difference between getDocuments() and getDocumentChanges() in Firestore's QuerySnapshot and when to use them?
The getDocuments() method:
Returns the documents in this QuerySnapshot as a List in order of the query.
While getDocumentChanges() method:
Returns the list of documents that changed since the last snapshot.
So each method does a different operation.
Edit:
In the below code, is it returning all documents or only added/modified/removed documents? Because It's like we are getting all documents and then sorting them according to their state. Is it correct?
It will get all documents when you are first opening the app, and unfortunately, this behavior cannot be changed. Right after that, each method will fire according to the performed operation. For example, if a document is added, you'll only get a single document in the ${dc.document.data}.

Flutter Firestore - How to get data from a Document Reference in a Document Field?

I'm building a Self-learning app with differente questions types. Right now, one of the questions have a field containing a list of DocumentReferences:
In Flutter, I have the following code:
Query<Map<String, dynamic>> questionsRef = firestore
.collection('questions')
.where('lesson_id', isEqualTo: lessonId);
await questionsRef.get().then((snapshot) {
snapshot.docs.forEach((document) {
var questionTemp;
switch (document.data()['question_type']) {
....
case 'cards':
questionTemp = CardsQuestionModel.fromJson(document.data());
break;
....
}
questionTemp.id = document.id;
questions.add(questionTemp);
});
});
Now, with "questionTemp" I can access all the fields (lesson_id,options,question_type, etc..), but when it comes to the "cards" field, how Can I access the data from that document reference?
Is there a way to tell firestore.instance to get the data from those references automatically? Or do I need to make a new call for each one? and, if so, how can I do that?
Thank you for your support in advance!
Is there a way to tell firestore.instance to get the data from those
references automatically? Or do I need to make a new call for each
one?
No there isn't any way to get these documents automatically. You need to build, for each array element, the corresponding DocumentReference and fetch the document.
To build the reference, use the doc() method
DocumentReference docRef = FirebaseFirestore.instance.doc("cards/WzU...");
and then use the get() method on this DocumentReference.
docRef
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('Document exists on the database');
}
});
Concretely, you can loop over the cards Array and pass all the Futures returned by the get() method to the wait() method which "waits for multiple futures to complete and collects their results". See this SO answer for more details and also note that "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."

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.

Flutter Firebase: get documents where a particular fiels is not null

I am trying to fetch documents where callerId fields does not contain null value
I tries this
final collection =
_firestoreService.instance.collection(TIME_SLOTS_COLLECTION_NAME);
Query query = collection
.where("listenerId", isEqualTo: _userService.user!.firestoreId!)
.where("callerId", isNull: false)
.orderBy("startTime", descending: true);
print("after query");
But it returns nothing. Code after this statement does not run at all. It means after query does not get printed on the console. I am not sure what's the problem?
I tried this
final collection =
_firestoreService.instance.collection(TIME_SLOTS_COLLECTION_NAME);
Query query = collection
.where("listenerId", isEqualTo: _userService.user!.firestoreId!)
.where("callerId", isEqualTo: "caller1")
.orderBy("startTime", descending: true);
print("after query");
It runs but the first one does not. Does anyone know something about this?
From looking at the FlutterFire implementation of isNull:
if (isNull != null) {
assert(
isNull,
'isNull can only be set to true. '
'Use isEqualTo to filter on non-null values.');
addCondition(field, '==', null);
}
So it looks like your condition is invalid, and you should be seeing an error message in your program output. That also explains why the app stops working: the assertion fails, and thus your app crashes.
You can use isNotEqualTo (or >, >= and others) to filter here:
.where("callerId", isNotEqualTo: false)
What I find really helpful in cases such as this is to keep the table of supported data types and their sort order handy.

Exclude documents from firebase query using date

I have a model of posts which have a post time and expire time like so
class BuyerPost{
..some other properties...
final Timestamp postTime;
final Timestamp expireTime;
My intended outcome is that the expired posts will not be included in my query. If i could delete them from the database automatically the better but I would also work with just excluding them from my query.
I have tried to use the endAt and orderBy to achieve it but it failed
Stream <List<BuyerPost>> get sellerBuyerPosts {
try {
return buyerPostCollection.where('status',isEqualTo: 'available')
.orderBy('expireTime',descending: true)
.endAt([Timestamp.now()])
.snapshots()
.map(yieldSellerBuyerPosts);
}catch(e){
print(e.toString());
return null;
}
}
I also tried filtering on the client side but i got a type operator error because <,> is not available for time stamp
List<BuyerPost> yieldSellerBuyerPosts(QuerySnapshot snapshot) {
try {
return snapshot.documents.map((doc) {
return BuyerPost(
...some properties here...
postTime: doc.data['postTime']?? Timestamp(0,0),
expireTime: doc.data['expireTime']?? Timestamp(0,0),
);
}).where((post) =>
post.expireTime>Timestamp.now()
).toList();
}catch(e){
print(e.toString());
return null;
}
}
Am i using the wrong method or have i just made a slight error somewhere.
Don't use endAt in this query. That's for pagination, which you're not using here. If you want to constrain the range of values for a field, use another "where" filter to specify the range of values for that field:
return buyerPostCollection
.where('status', isEqualTo: 'available')
.where('expireTime', isLessThan: Timestamp.now())
.orderBy('expireTime', descending: true)
.snapshots()
The first method actually worked the only issue is that it does not work along with the stream. The only way you will see an update in post queries is with change of widget state, and you will find expired posts disappeared.

Resources