2

I am creating a flutter app that should query a Firestore collection and return results if two conditions are met. Here is my code:

Stream<List<FormQuestions>> get question {
  return someCollection
  .where('myQUestion', isEqualTo: 'nameQuestion')
  .snapshots()
  .map(_someQuestionListFromSnapshot);
}

If I do with just one .where() condition, it works fine. But with two, it gives no results although I have documents that meet both conditions. I would like it to return for multiple .where() conditions like so:

Stream<List<FormQuestions>> get question {
  return someCollection
  .where('myQUestion', isEqualTo: 'nameQuestion')
  .where('myQuestion', isEqualTo: 'ageQuestion')
  .snapshots()
  .map(_someQuestionListFromSnapshot);
}

Is there a way to add an OR(||) operator or how can I do this so that I get results for both "nameQuestion" and "ageQuestion"? Kindly assist. Thanks.

David
  • 473
  • 1
  • 4
  • 14

3 Answers3

9

Firestore does not support regular OR queries, but it does support IN queries with which you can implement what you need.

You query would look like citiesRef.where('country', 'in', ['USA', 'Japan']);

someCollection
  .where('myQUestion', whereIn: ['nameQuestion', 'ageQuestion'])

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 1
    This worked for me when I was querying one field. But then it gives me an error whenever I add a second field. I would like to have an OR for the myQuestion field and location field then query with an AND for results of myQuestion and location. something like this: .where('myQuestion', whereIn:['QuestionOne', 'QuestionTwo']) .where('location', whereIn: ['locationOne', 'locationTwo']) – David Apr 22 '20 at 15:01
  • The code in your question seems to use only one field `myQUestion`. Did I misunderstand that? If you want to OR across multiple fields, that is indeed not possible. See the question I linked in my answer for more on that. – Frank van Puffelen Apr 22 '20 at 15:12
2

You can do the following:

With getDocuments():

  void getData() async {
    List<Future<QuerySnapshot>> futures = [];

    var firstQuery = someCollection
        .where('myQUestion', isEqualTo: 'nameQuestion')
        .getDocuments();

    var secondQuery = someCollection
        .where('myQuestion', isEqualTo: 'ageQuestion')
        .getDocuments();

    futures.add(firstQuery);
    futures.add(secondQuery);

    List<QuerySnapshot> results = await Future.wait(futures);
    results.forEach((res) {
      res.documents.forEach((docResults) {
        print(docResults.data);
      });
    });
  }

So here you preform an OR query, and return the result from the two queries. Future.wait(futures) will wait until both of these queries are finished and return a `List that will contain the documents that satisfy these two queries.


With snapshots():

import 'package:async/async.dart' show StreamGroup;
///

        getData() async {
          List<Stream<QuerySnapshot>> streams = [];
          final someCollection = Firestore.instance.collection("users");
          var firstQuery = someCollection
              .where('myQUestion', isEqualTo: 'nameQuestion')
              .snapshots();

          var secondQuery = someCollection
              .where('myQuestion', isEqualTo: 'ageQuestion')
              .snapshots();

          streams.add(firstQuery);
          streams.add(secondQuery);

          Stream<QuerySnapshot> results = StreamGroup.merge(streams);
          await for (var res in results) {
            res.documents.forEach((docResults) {
              print(docResults.data);
            });
          }
        }

snapshots() returns a Stream which is a source of asynchronous data events, so it will keep listening for incoming data. To merge the two or query, you need to use StreamGroup.merge(streams) which merges the events from streams into a single single-subscription stream. Then using await for you can iterate over the events of a stream like the for loop iterates over an Iterable.

Check:

https://dart.dev/tutorials/language/streams

Peter Haddad
  • 78,874
  • 25
  • 140
  • 134
  • 1
    This works, but when I try to use another field to have a compound query so that: I have nameQuestion OR ageQuestion AND locationQuestion OR regionQuestion I get results that respond only to the first two queries and not the second set of queries – David Apr 22 '20 at 13:17
  • How are u writing the queries – Peter Haddad Apr 22 '20 at 13:21
  • var firstQuery = someCollection .where('myQuestion', isEqualTo: 'nameQuestion') .snapshots(); var secondQuery = someCollection .where('myQuestion', isEqualTo: 'ageQuestion') .snapshots(); var thirdQuery = someCollection .where('location', isEqualTo: 'locationQuestion') .snapshots(); var fourthQuery = someCollection .where('location', isEqualTo: 'regionQuestion') .snapshots(); – David Apr 22 '20 at 13:24
  • This is 4 or queries – Peter Haddad Apr 22 '20 at 13:27
  • there is an array query that works but just for one field .where('myQuestion', whereIn: ['nameQuestion', 'ageQuestion']) If i do it like this, it works. but when I add a second array query so that it is .where('myQuestion', whereIn: ['nameQuestion', 'ageQuestion']) .where('location', whereIn: ['locationQuestion', 'regionQuestion']) Then it doesnt work and throws a "you cannot use more than one 'in' filter – David Apr 22 '20 at 13:30
  • You should do 2 queries. First query has myQuestion==nameQuestion and location==locationQuestion then another query with the other two – Peter Haddad Apr 22 '20 at 13:31
  • Lemme try this. thanks – David Apr 22 '20 at 13:36
  • `var firstQuery = someCollection .where('myQUestion', isEqualTo: 'nameQuestion') .where('location', isEqualTo: 'locationQuestion') .snapshots(); var secondQuery = someCollection .where('myQuestion', isEqualTo: 'ageQuestion') .where('location', isEqualTo: 'regionQuestion') .snapshots();` use this – Peter Haddad Apr 22 '20 at 16:03
  • this is exactly what you want. The first query will check if location== locationQuestion AND myQuestion==nameQuestion. The second query will check if location==regionQuestion AND myQuestion==ageQuestion. So if any document satisfies the first query then u retrieve it. Then if any document satisfies the second query then u retrieve it and all is merged in the stream – Peter Haddad Apr 22 '20 at 16:05
  • @PeterHaddad Your answer is very helpful but I am getting error in console "type 'Future' is not a subtype of type 'Stream>?'" because StreamBuilder needs `stream` as type of `Stream>` and If we use `async` keyword with function name it converts it to `Future` type if I set `results` as return results. You can see my created function in next comment. – Kamlesh Jun 16 '21 at 11:14
  • _messagesStream() async { List> streams = []; final someCollection = FirebaseFirestore.instance.collection("messages"); var firstQuery = someCollection .where('encSenderUId', isEqualTo: _loggedInUserId) .where('encReceiverUId', isEqualTo: widget.encUId) .snapshots(); var secondQuery = someCollection .where('encSenderUId', isEqualTo: widget.encUId) .where('encReceiverUId', isEqualTo: _loggedInUserId) .snapshots(); streams.add(firstQuery); streams.add(secondQuery); Stream results = StreamGroup.merge(streams); return results; } – Kamlesh Jun 16 '21 at 11:15
  • and calling this function in build function like `StreamBuilder( stream: _messagesStream(), builder: (context, AsyncSnapshot snapshot) {....` – Kamlesh Jun 16 '21 at 11:16
  • @Kamlesh try to ask it as another question with details and good format, it's really hard to read this in the comment section, have you tried using `in` query? – Peter Haddad Jun 16 '21 at 12:11
0

You cannot find a document using both where() calls because you are querying on the same myQUestion property. There is no way a property can hold two values at the same time. It can hold one or the other. You might be looking for an OR operator but using an AND in the way you do, it's not possible. An AND will only work when you query on different properties.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • 1
    My Bad. I am looking for an OR. Is there a way to do it? – David Apr 22 '20 at 10:58
  • Check janstol's answer from this [post](https://stackoverflow.com/questions/58191550/flutter-dart-querysnapshot-isequalto-or-operator). – Alex Mamo Apr 22 '20 at 11:14
  • Have you solved the issue? – Alex Mamo Apr 23 '20 at 09:21
  • Still haven't figured it out. I have been told to use the merge method but that is going to be very hard considering I have very many fields. The zip method isn't working well for me either. I may be forced to create several collections and have simple queries for each instead – David Apr 23 '20 at 17:27
  • That's a good idea. You can also use [denormalization](https://stackoverflow.com/questions/54258303/what-is-denormalization-in-firebase-cloud-firestore/54258505#54258505) if it's needed. – Alex Mamo Apr 24 '20 at 08:44