15

Since there is no logical OR operator in Firestore, I am trying to merge 2 separate queries locally.

Now I wonder how I can keep up the proper order of the results. When I run 2 queries independently, I can't oder the results specificly (at least not the order in which I get the results from Firestore with the orderBy method).

My idea was to put the 2nd query inside the onSuccessListener of the 1st query. Is this a bad idea performance wise?

public void loadNotes(View v) {
    collectionRef.whereLessThan("priority", 2)
            .orderBy("priority")
            .get()
            .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
                @Override
                public void onSuccess(QuerySnapshot queryDocumentSnapshots) {

                    for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
                        Note note = documentSnapshot.toObject(Note.class);
                        //adding the results to a List
                    }

                    collectionRef.whereGreaterThan("priority", 2)
                            .orderBy("priority")
                            .get()
                            .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
                                @Override
                                public void onSuccess(QuerySnapshot queryDocumentSnapshots) {

                                    for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
                                        Note note = documentSnapshot.toObject(Note.class);
                                        //adding the results to a List
                                    }
                                }
                            });
                }
            });
}
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Florian Walther
  • 6,237
  • 5
  • 46
  • 104

1 Answers1

15

To merge 2 separate queries locally, I recommend you to use Tasks.whenAllSuccess() method. You can achieve this, using the following lines of code:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query firstQuery = rootRef...
Query secondQuery = rootRef...

Task firstTask = firstQuery.get();
Task secondTask = secondQuery.get();

Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
    @Override
    public void onSuccess(List<Object> list) {
         //Do what you need to do with your list
    }
});

As you can see, when overriding the onSuccess() method the result is a list of objects which has the exact order of the tasks that were passed as arguments into the whenAllSuccess() method.

There is also another approach and that would be to use Tasks.continueWith() method. But according to the use-case of your app, you can use eiter whenAllSuccess() method or continueWith() method. Please see here the official documentation.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thank you so much! I actually found the same solutions online, but I was highly insecure if they are correct, because I found so little examples. Now that you name the exact same methods, I have insurance. One thing: I am a newbie so I have problems with generics, but I noticed that when I write `Task> combinedTask` instead of declaring the type just as `Task`, I get a `List` in OnSucess, instead of a `List – Florian Walther May 02 '18 at 10:27
  • [QuerySnapshot](https://developers.google.com/android/reference/com/google/firebase/firestore/QuerySnapshot) class, like every other class in Java extends the `Object` class. Class Object is the root of the class hierarchy. Every class has Object as a superclass. So `QuerySnapshot` is-a Object. The `QuerySnapshot` a related type in ths case, so feel free to use `List`. – Alex Mamo May 02 '18 at 11:26
  • Ok, but is this better than casting the `objects` in `onSuccess` or does it not matter? – Florian Walther May 02 '18 at 11:29
  • Is no difference at all. Please see this [post](https://stackoverflow.com/questions/6916538/casting-and-generics-any-performance-difference). – Alex Mamo May 02 '18 at 11:50
  • I just try to understand why it returns that as a List of Objects in the first place. Is it because I can pass different kind of Tasks to whenAllSuccess? – Florian Walther May 02 '18 at 14:55
  • It returns a List of Objects because you are not indicating a proper class at all. So the generic class for the list, is set to the root class of all objects. Every class in Java "is-a" Object. When you are using generics you are indicating the exact type of the containing objects so the list will contain objects of type `QuerySnapshot` and not Object. Right? – Alex Mamo May 02 '18 at 15:44
  • Right, but the `get` method on a `CollectionReference` returns a `Task – Florian Walther May 02 '18 at 15:45
  • Yes, you're right. This is happening because the [onComplete()](https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/tasks/OnCompleteListener) is defined as `onComplete(Task task)` and when is called on a `CollectionReference` it will only return `Task` but when called on `Tasks.whenAllSuccess(firstTask, secondTask)` can return a List of different types of objects. Android Studio doesn't know what kind of objects might be and that's why it returns a generic list of objects, right? – Alex Mamo May 02 '18 at 16:32
  • Right, I understand why Android Studio sets the type like this. As a beginner it's sometimes just hard to figure out best practices, and I wonder if defining the generic type just for the sake of code readability (like I do in my approach) is reasonable. But from what I read about generic types, one benefit is to avoid casting, so it seems reasonable. – Florian Walther May 02 '18 at 16:38
  • Yes it is. You can go ahead in this way. Do you think that my answer helped you? – Alex Mamo May 02 '18 at 16:39
  • Yes, it solved the question and was very helpful! Thank you! – Florian Walther May 02 '18 at 16:42
  • @AlexMamo Hi, Can I do pagination here? limit 10 on both queries? and get 20 results in combined task? – Surabhi Choudhary Feb 16 '20 at 10:32
  • @SurabhiChoudhary You can check **[this](https://stackoverflow.com/questions/50281966/firebase-firestore-subcollection-of-documents-from-document)** and **[this](https://stackoverflow.com/questions/50592325/is-there-a-way-to-paginate-queries-by-combining-query-cursors-using-firestorerec/50692959)** out. – Alex Mamo Feb 17 '20 at 08:33
  • In cases like this, how are read charges calculated? Is it for both the result set or for combined one? Like in first filter we get 20 results and in second after more filtering we get 10, so we will be charged for 10, 20 or 30? – Surabhi Choudhary Feb 27 '20 at 14:17
  • @SurabhiChoudhary You will be charged with the combined number of elements that are returned by the queries. – Alex Mamo Feb 27 '20 at 14:20
  • Thanks @AlexMamo for replying so fast. Really obliged. Last question, can this be used with snapshot listeners? – Surabhi Choudhary Feb 27 '20 at 14:45
  • @SurabhiChoudhary No, only when you use a `get()` call, since `get()` returns a `Task` object. – Alex Mamo Feb 27 '20 at 14:48
  • @SurabhiChoudhary how did you cast it to a QuerySnapshot, could you post an example please. – Sabeeh Ul Haq Mar 28 '20 at 18:50
  • @SabeehUlHaq I'm not sure I understand the question. If you have a hard time implementing that, post a new question and show us what you have tried. In this way, I or other developers will be able help you. – Alex Mamo Mar 29 '20 at 13:30
  • @AlexMamo so the answer on Feb 27 '20 would be 10 or 30? – fkvestak Mar 30 '21 at 12:35
  • @fkvestak 10 or 30 what? – Alex Mamo Mar 30 '21 at 12:41
  • @AlexMamo someone asked will we be charged for 10, 20 or 30 results in his example? Let's say my first query returns 200 results, second one returns 50, third one returns 10. I will render these 10 items because they satisfy all 3 queries. But, how much will I be charged? 10 document reads, or 260? – fkvestak Mar 30 '21 at 13:00
  • 1
    @fkvestak You'll **always** be charged with a number of read operations that is equal to the number of elements that are returned. If you have, for example, three queries, and each one of them returns three documents, you'll be charged with 9 document reads. – Alex Mamo Mar 30 '21 at 13:04
  • @AlexMamo that's what I was afraid about. That is really bad, which makes Firestore basically useless, even for a basic TODO app. – fkvestak Mar 30 '21 at 13:14
  • 1
    @fkvestak I think this [article](https://medium.com/firebase-tips-tricks/how-to-drastically-reduce-the-number-of-reads-when-no-documents-are-changed-in-firestore-8760e2f25e9e) will provide even more info. It really isn't what you say. – Alex Mamo Mar 30 '21 at 13:18
  • @AlexMamo your article looks really nice, I will get into it this evening ;) – fkvestak Mar 30 '21 at 13:36
  • @fkvestak Good to hear that ;) – Alex Mamo Mar 30 '21 at 14:05
  • @AlexMamo Do you have informations/experience about running 2 queries? Everyones always saying best practise is running 1 query & duplicate data instead. Me for example im building a dating app & i want to show clients profile recommendations. I need to make sure they didnt see the profile yet or liked it. Im saving the user likes in an own collection. Therefor I would get 5 profiles, take the ids and check if they are in the users collection (10reads). Makes more sense for me than saving all the data again somewhereelse on the user, I still wouldnt be able to get my results. what do you think – Marcel Dz Mar 31 '21 at 07:16
  • @MarcelDz Might work for sure your scenario, but if have something special, other than running multiple queries (which is quite normal), please post a new question here on Stackoverflow using its own [MCVE](https://stackoverflow.com/help/mcve), so I and other Firebase developers can help you. – Alex Mamo Mar 31 '21 at 07:22
  • @AlexMamo alright I did so, https://stackoverflow.com/questions/66884058/firebase-firestore-flutter-run-multiple-queries-vs-denormalize-data waiting eagerly for your input. Greetings Marcel – Marcel Dz Mar 31 '21 at 08:19