1

I can easily paginate a Recyclerview with the Firestore collection. But in my case, I have three dependent collections. They are

  1. product (10000000 entries expected)
  2. category (100000 entries expected)
  3. region (50000 entries expected)

In Recyclerview I have to show details of product, category, and region. As we know, there is no option for inner join queries like SQL. I have category id and region id stored in product details. Based on the id, I need to filter and sort products. Also based on the region I need to change the currency in the product list. How can I achieve these kinds of complex architecture with Firestore to meet normal SQL features? In my case, Firestore is confirmed.

Product

{
    "productId": 122,
    "productName": "product name 1",
    //other product details here
    "categoryId": 758,
    "regionId": 395
}

Category

{
    "categoryId": 90474,
    "categoryName": "category name 200",
    //other category configuration details here
}

Region

{
    "regionId": 2372,
    "regionName": "tokyo",
    //other region details here
}

enter image description here

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
KIRAN K J
  • 632
  • 5
  • 28
  • 57

1 Answers1

2

To be able to perform a query based on a category and a region, you should first read their IDs. So assuming that a user selects "category name 200" and the "tokyo" for the region, in order to get the corresponding products, please use the following lines of code:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference categoryRef = rootRef.collection("Category");
CollectionReference regionRef = rootRef.collection("Region");
CollectionReference productRef = rootRef.collection("Product");

categoryRef.whereEqualTo("categoryName", "category name 200").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        if (task.isSuccessful()) {
            for (QueryDocumentSnapshot document : task.getResult()) {
                String categoryId = document.getString("categoryId");
                regionRef.whereEqualTo("regionName", "tokyo").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            for (QueryDocumentSnapshot document : task.getResult()) {
                                String regionId = document.getString("regionId");

                                //Perform a query to get the products
                                productRef.whereEqualTo("categoryId", categoryId).whereEqualTo("regionId", regionId).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                                    @Override
                                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                                        if (task.isSuccessful()) {
                                            for (QueryDocumentSnapshot document : task.getResult()) {
                                                String productName = document.getString("productName");
                                                Log.d(TAG, productName);
                                            }
                                        } else {
                                            Log.d(TAG, "Error getting documents: ", task.getException());
                                        }
                                    }
                                });
                            }
                        } else {
                            Log.d(TAG, "Error getting documents: ", task.getException());
                        }
                    }
                });
            }
        } else {
            Log.d(TAG, "Error getting documents: ", task.getException());
        }
    }
});

Be sure to have unique categories and regions, so that each query returns a single result.

If you need to order the results, you can also chain an orderBy() call, to order the products according to a specific property. Be also aware that an index is required for such a query.

Please also see below a simple solution to paginate the results in Firestore:

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • My doubt basically for the Recyclerview. In each row can we process like above? Also there is pagination. – KIRAN K J Aug 06 '21 at 15:27
  • I'm not sure I understand "In each row can we process like above?". Basically, the query that returns the products should be paginated, right? Have you tried my solution above? Does it work? – Alex Mamo Aug 07 '21 at 08:49
  • In each row we can do like the above. But there will be three calls per row. That means, if we are fetching 25 rows, then we need to process 75 Firestore request listeners. It seems not an efficient way to the processor and network requests. What do you think? – KIRAN K J Aug 07 '21 at 10:04
  • 75 listeners, it's not something you should worry about. Listeners are cheap. So you can go ahead with that. – Alex Mamo Aug 07 '21 at 11:10
  • Hi Alex, Thank you for your great support. I am checking the answer with different scenarios based on my use case. I will definitely approve once my tests are completed. – KIRAN K J Aug 09 '21 at 07:06
  • 1
    Ok, keep me posted. – Alex Mamo Aug 09 '21 at 07:08
  • I tried to implement your solution. But I cannot implement it right now. I assume it will perform well while implementing the filtering feature. But I am stuck in the listing section. Each product has categoryId and regionId. Based on those ids we need to fill product objects with corresponding category and region names for showing recylerview. How can I get it? . Because the names are getting inside corresponding addOnCompleteListener. So after put those details to product object we need to set the product list data – KIRAN K J Aug 10 '21 at 15:41
  • That's the reason why I have used nested callbacks in my solution. As soon as the `categoryId` is finished loading, we get the `regionId`. As soon as the `regionId` is finish loading we get the product that corresponds to both, `categoryId` and `regionId`. inside the last callback, you get a list of Product objects. As soon as you get the, pass it to an adapter and display the products in the RecyclerView. – Alex Mamo Aug 10 '21 at 16:29
  • As I mentioned in the previous comment, I need not filter by category and region at the very first time while loading products. At initial loading, we have an N number of products. Then we have to fetch category name and region name and add those details to each product object. That is my first requirement. But after applying filters(region and/or category), your solution will work. But I am waiting to resolve my first problem – KIRAN K J Aug 11 '21 at 13:19
  • Did I missed anything or can you follow my exact issue? – KIRAN K J Aug 12 '21 at 04:52
  • I am very happy that you are helping me a lot. Thank you very much. I updated the question with my listing and filtering scenario. Because most of the people will be stuck in the first problem than the second one. I had also having priority to the first problem. but while writing the question initially, I thought that the filter issue answer can solve my first problem itself. But now I came to know that both having different solutions. I am sorry to say that, I edited my question for getting a complete solution from me to others. Also, I cannot verify the filtering without test 1st issue. – KIRAN K J Aug 12 '21 at 09:00
  • This answer already contains the solution to filtering the "Category" and "Region" collections by their names. If you want to get those values ahead of time, you can use two Task objects, as explained in this [answer](https://stackoverflow.com/questions/50118345/firestore-merging-two-queries-locally/50132229) and right after that perform the third query. – Alex Mamo Aug 12 '21 at 09:44
  • Editing a question with other related stuff is not a recommended practice, here on Stackoverflow. So I just rolled back your edited question. New questions that derive from the initial question, should be considered new questions. So please post a new one, using its own [MCVE](https://stackoverflow.com/help/mcve), so I and other Firebase developers can help you. Please remember to explain what you're trying to do, including the code that doesn't work the way you expect, and what you've done to debug it. – Alex Mamo Aug 12 '21 at 09:45
  • I am sorry. I cannot fix my problem with this question. i will create another one. I hope this answer will help me in sorting. So I am accepting this answer. Thank you – KIRAN K J Aug 12 '21 at 09:56
  • 1
    You're very welcome, Kiran. Looking for to seeing your new question. – Alex Mamo Aug 12 '21 at 09:57
  • I have created another question with more details about my primary problem. https://stackoverflow.com/questions/68758783/how-can-i-inner-join-firestore-collections-for-a-paginated-listview – KIRAN K J Aug 12 '21 at 13:54
  • 1
    @KIRANKJ I'll take a look and if I'll know the answer, I'll write to you. – Alex Mamo Aug 12 '21 at 14:54