Cloud Firestore inequality operator exception flutter - firebase

while i have been using cloud firestore in my flutter app, strange exception occured.
EDITED
this is my code:
Stream<List<Product>> productsStream(int id) async* {
final k = _db
.collection('products')
.where('category_id', isEqualTo: id)
.where('stock', isGreaterThanOrEqualTo: 1)
.orderBy('order')
.snapshots();
yield* k.map((event) => event.docs
.map((e) => Product.fromJson(
e.data(),
))
.toList());
Here what i would like to achieve is to check for a product wether it is in stock and then to order products in an ascending order by order field in my products collection.
But i am receiving this strange error:
Exception:
'package:cloud_firestore/src/query.dart': Failed assertion: line 421 pos 16: 'field == orders[0][0]': The initial orderBy() field '[[FieldPath([order]), false]][0][0]' has to be the same as the where() field parameter 'FieldPath([stock])' when an inequality operator is invoked.
What might be solution?

This is explained in the ordering limitations documentation:
If you include a filter with a range comparison (<, <=, >, >=), your first ordering must be on the same field
So I suspect you should have:
.where('category_id', isEqualTo: id)
.where('stock', isGreaterThanOrEqualTo: 1)
.orderBy('stock')
.orderBy('order')
Obviously that means it's no longer primarily ordered by order. You'd need to do local sorting if that's a problem - in which case you may find you don't want to order server-side at all.
Although "not equal to" filters aren't mentioned in the documentation, it sounds like they also count as range comparisons in terms of prohibiting filtering.
So basically, I would suggest you either need to filter locally on stock, or you need to order locally on order - you can't do both in the same Firestore query at the moment. Note that this server-side limitation is something that could change in the future (either for all range comparisons, or possibly just to allow "not equal to") so it may be worth retesting periodically.

You may need to do these two things. (it worked for me as shown in the example down below).
1 - Add an index for stock as Ascending in the console. This index should be in ascending.
2 - You must query based on that index.. So, as opposed to doing
...
.where('stock', isGreaterThanOrEqualTo: 1)
...
You should be doing
...
.orderBy('stock', descending: false)
.where('stock', isGreaterThanOrEqualTo: 1)
...
This applies to all other composite indexes that have equality checks. So you may need to do that for category_id
Also, here's my code for example
For context. I'm displaying a chat list, where the number of messages are greater than 0, and of-course, where the current user is a participant
1 - In GCP https://console.cloud.google.com/firestore/indexes/composite?project=**your-project-name**
I added the required index as such
Composite index setup
2 - Then finally, in my flutter code
static Future<QuerySnapshot> getUsersChatsList(String userId,
{DocumentSnapshot? startAfterDoc, int limit = 10}) =>_chatsRef
.where('participants_ids', arrayContains: userId)
.orderBy('message_count', descending: false)
.where('message_count', isNotEqualTo: 0)
.orderBy('last_message_created_at', descending: true)
.limit(limit)
.get();
Ignore the limit & startAfterDoc as that was done for pagination
NOTICE that I had to manually order message_count in ASC before checking the condition.
And finally, of course, they're sorted in DESC in time.

You should make sure that you are ordering with the same constraint as your where like this:
getInventory() async {
String value = user.uid;
return inventoryFirebaseReference
.orderBy('availableStock')
.where('userFirebaseId', isEqualTo: value)
.orderBy('availableStock')
.where('availableStock', isGreaterThan: 0)
.snapshots()
.listen((event) {
chargeProducts = false;
final _documentList = event.docs;
//print(_documentList.length);
_documentList.map((e) => {});
List<ProductMoof> listProductsProv = _documentList.map((doc) {
ProductMoof inventoryItemProv =
ProductMoof.fromMap(doc.data() as Map<String, dynamic>);
inventoryItemProv.firebaseId = doc.id;
return inventoryItemProv;
}).toList();
listInventory = listProductsProv;
notifyListeners();
print('Update gotten from Menu');
});
}

Related

firestore using "where" and "orderBy"

I am trying to run a simple query on Firestore, using both where and orderBy. The query does not work. After some research, I found out that I might need to build index in Firestore. I did that, but once again, this query does not work.
error result
The initial orderBy() field "[[FieldPath([lastTime]), true]][0][0]" has to be the same as the where() field parameter "FieldPath([lastChat])" when an inequality operator is invoked. 'package:cloud_firestore/src/query.dart': package:cloud_firestore/src/query.dart:1 Failed assertion: line 456 pos 13: 'field == orders[0][0]
here is the code:
Stream<QuerySnapshot<Map<String, dynamic>>> chatsStream(String email) {
var result = firestore
.collection('users')
.doc(email)
.collection("chats")
.where("lastChat", isNotEqualTo: "")
.orderBy("lastTime", descending: true)
.snapshots();
return result;
}
and this is index in firestore:
sorry for my english, thanks.
You need to specify an orderBy for the lastChat field, as isNotEqualTo is a range condition (it helps to think of it as <> for that purpose):
var result = firestore
.collection('users')
.doc(email)
.collection("chats")
.orderBy("lastChat")
.where("lastChat", isNotEqualTo: "")
.orderBy("lastTime", descending: true)
.snapshots();
The index on lastTime is ascending, but your query is descending. That means the index doesn't match the query, and the database will return no results.
It should actually also log a warning/error with a direct link to create the index with all fields pre-populated (including the correct sort order for lastTime).

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.

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.

Cloud Firestore OR query

How do I fetch multiple documents from firebase? I want to fetch all of the documents that are in a list.
I can only run the below query if instead of a list I pass it a single item for the owner field since isMemberOf is not a real operator. I put it there as something that I imagine it might look like.
Right now I can only pass a single value and use isEqualTo: instead of my made up operator.
How do I do this?
Stream<List<Event>> subscribeToEventsByDates(
List<String> users, DateTime start, DateTime end) {
return Firestore.instance
.collection('events')
----> SELECT * (all that match) <------- doesn't exist? How to do this?
.where('owner', isMemberOf: users)
.where('start', isGreaterThanOrEqualTo: start)
.where('start', isLessThanOrEqualTo: end)
.snapshots()
.map((snapshot) => snapshot.documents
.map((event) => Event.fromJson(event.data))
.toList());
}
If you want to pass an array of strings to a filter to match any one of them for a given field, that's called an "in" query.
The API documentation for where() suggsets that you should use "whereIn" for that.
.where('owner', whereIn: users)
From the documentation on in queries:
Use the in operator to combine up to 10 equality (==) clauses on the same field with a logical OR.

Resources