3

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.

Here is my collection fields

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?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
RuslanBek
  • 1,592
  • 1
  • 14
  • 30
  • Does this answer your question? [Firestore multiple range query](https://stackoverflow.com/questions/50658651/firestore-multiple-range-query) – Simon Sot Mar 27 '21 at 09:15
  • @SimonSot no it is not i assume. – RuslanBek Mar 27 '21 at 09:47
  • That's not s strange error - it's correct based on your code and covered in the Firestore Documentation [Order Limitations](https://firebase.google.com/docs/firestore/query-data/order-limit-data#limitations) *If you include a filter with a range comparison (<, <=, >, >=), your first ordering must be on the same field* so you would have to change your orderBy to 'stock' to remove the error. – Jay Mar 27 '21 at 14:29
  • @Jay By strange i meant that it is unclear error for me bc i am new on FB. thanks for clarifying. i know that if i change `orderBy('order')` to `orderBy('stock')` it will got fixed. but what i would like to achieve is simultaneously check for a product quantity in stock and then to order products in an ascending order using `order` field in my `products` collection. how can i achieve that? could you give a hand? – RuslanBek Mar 27 '21 at 16:45

4 Answers4

13

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • okay so when i do format my code like this `_db.collection('products').where('category_id', isEqualTo: id).orderBy('stock').where('stock', isNotEqualTo:0).orderBy('order', descending: false).snapshots();` then it will sort in the way that products whose **stock** quantity is 0 won't appear and will be ordered by **order** field in an ascending order simultaneously? – RuslanBek Mar 27 '21 at 09:41
  • @Ruslanbek0809: No, that code would still order by `stock` first - unless you *want* to order by stock, you should remove the `.orderBy('stock')` part. (As an aside, I would suggest putting all of the filtering first, then all of the ordering... it's simpler to read that way.) – Jon Skeet Mar 27 '21 at 09:48
  • i have done what you have suggested. but still same error. Could you please reread my question again. i have **edited** it – RuslanBek Mar 27 '21 at 10:06
  • @Ruslanbek0809: Your edited code still has `.where('stock', isGreaterThanOrEqualTo: 1)`, which is the cause of the problem. – Jon Skeet Mar 27 '21 at 10:13
  • `isNotEqualTo` is also giving the same error. what might be the problem? – RuslanBek Mar 27 '21 at 11:18
  • @Ruslanbek0809: That sounds like it counts as a range filter, unfortunately. If you change it to `isEqualTo` (just for test purposes) does the query then succeed? (Obviously it won't give you the results you want, but that's a different matter.) I'm surprised by it failing though... I might try the same thing myself later on in C#. – Jon Skeet Mar 27 '21 at 11:21
  • It is working with `isEqualTo`. i still didn't get what might be real problem( isn't there workaround? – RuslanBek Mar 27 '21 at 11:42
  • @Ruslanbek0809: It sounds like that's a filter limitation then, basically. The only workarounds I can think of would be to either filter locally or sort locally. – Jon Skeet Mar 27 '21 at 11:49
  • i will omit orderBy from above query and will do ordering locally after data fetch. how can i order locally by ascending order? any help? – RuslanBek Mar 27 '21 at 12:52
  • @Ruslanbek0809: I don't know Dart at all, so can't help you with that... and to be honest, that's a pretty unrelated question to how you'd do it server-side. – Jon Skeet Mar 27 '21 at 14:00
  • thanks for your effort and help. you informed me well. i will try workaround. – RuslanBek Mar 27 '21 at 16:48
0

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.

0

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');
    });
  }
0
Stream<List<Product>> productsStream(int id) async* {
final k = _db
    .collection('products')
    .where('category_id', isEqualTo: id)
    .orderBy('category_id')
    .where('stock', isGreaterThanOrEqualTo: 1)
    .orderBy('stock')
    .orderBy('order')
    .snapshots();
     
yield* k.map((event) => event.docs
    .map((e) => Product.fromJson(
          e.data(),
        ))
    .toList());