0

In my Firestore database, I have a 'categories' collection with multiple documents which in turn have 'products' sub-collections with respective documents.

enter image description here

enter image description here

I'm trying to get all documents in all sub-collections and populate into a Recylerview but I have failed.

Here is the code I have;

 private void populateAllProducts() {
        Log.d(TAG, "populateAllProducts called: ");
          database
                .collection(Globals.CATEGORIES)
                .get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                            for (QueryDocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
                                Category category = document.toObject(Category.class);
                                Log.d(TAG, "Category: " + category.getName());
                                    database
                                        .collection(Globals.CATEGORIES)
                                        .document(category.getUuid())
                                        .collection(Globals.PRODUCTS)
                                        .get()
                                        .addOnCompleteListener(task1 -> {
                                            if (task.isSuccessful()) {
                                                    for (QueryDocumentSnapshot snapshot : Objects.requireNonNull(task1.getResult())) {
                                                        Product product = snapshot.toObject(Product.class);
                                                        Log.d(TAG, "All products: " + product.getName());
                                                    }
                                                }
                                        })
                                        .addOnFailureListener(e -> Log.e(TAG, "Error: ", e));
                            }
                    }
                })
                .addOnFailureListener(e -> {
                    vars.verityApp.crashlytics.log("Error while fetching categories");
                    Log.e(TAG, "Error occured: ", e);
                    vars.verityApp.crashlytics.recordException(e);
                });
    }

But I receive this Fatal Exception;

2020-08-11 16:27:16.358 26161-26161/com.verityfoods E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.verityfoods, PID: 26161
    java.lang.NullPointerException: Provided document path must not be null.
        at com.google.firebase.firestore.util.Preconditions.checkNotNull(Preconditions.java:147)
        at com.google.firebase.firestore.CollectionReference.document(CollectionReference.java:103)
        at com.verityfoods.ui.bottomviews.shop.ShopFragment.lambda$populateCategories$2$ShopFragment(ShopFragment.java:87)
        at com.verityfoods.ui.bottomviews.shop.-$$Lambda$ShopFragment$dIuQdpUfodkE_zdMSo4L2Rr4DL0.onComplete(Unknown Source:2)
        at com.google.android.gms.tasks.zzj.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

I will appreciate your help!

Denny
  • 991
  • 12
  • 20

2 Answers2

3

You are getting the following error:

java.lang.NullPointerException: Provided document path must not be null.

Because you are passing a null value in your reference. Most likely category.getUuid() returns null, hence that error. Seeing your getUuid() getter, I'm can guess that the name of that property is not uuid in the database, so this is most likely the reason.

I'm trying to get all documents in all sub-collections and populate into a Recylerview

To get all the data in all products subcollections, you should use a Firestore collection group query and not regular CollectionReference object. In code, this query should look like this:

db.collectionGroup("products").get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
    @Override
    public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
            //Iterate to get the products out of the queryDocumentSnapshots object
        }
    });
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thanks @Alex Mamo, Lemme try that second option. I will get back here for the results. – Denny Aug 11 '20 at 14:04
  • Thanks Alex, this is working, I have logged the result and it has all the documents, Thanks a lot. You have saved me. – Denny Aug 11 '20 at 14:20
  • 1
    @DenisOluka If this answer fixes your issue, you should accept it (click the check mark next to it). That does two things. It lets everyone know your issue has been resolved to your satisfaction, and it gives the person that helps you credit for the assist. [See here](http://meta.stackexchange.com/a/5235) for a full explanation. – isaaaaame Aug 11 '20 at 14:30
  • Sorry, I didn't do that immediately, but I have done that right away. – Denny Aug 11 '20 at 14:53
  • @AlexMano, on the above answer, the database may get bigger and bigger with time, meaning querying too much data to the recyclerview will be slower. Is there a way of using ```FirestorePagingOptions``` on ```collectionGroup``` – Denny Aug 11 '20 at 14:56
  • @DenisOluka Yes, you should definitely implement [pagination](https://stackoverflow.com/questions/50741958/how-to-paginate-firestore-with-android). – Alex Mamo Aug 11 '20 at 14:57
0

You are checking if the FIRST task it's done inside of the second task.

private void populateAllProducts() {
        Log.d(TAG, "populateAllProducts called: ");
          database
                .collection(Globals.CATEGORIES)
                .get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                            for (QueryDocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
                                Category category = document.toObject(Category.class);
                                Log.d(TAG, "Category: " + category.getName());
                                    database
                                        .collection(Globals.CATEGORIES)
                                        .document(category.getUuid())
                                        .collection(Globals.PRODUCTS)
                                        .get()
                                        .addOnCompleteListener(task1 -> {
                                            if (task.isSuccessful()) { //Here should be task1, not task
                                                    for (QueryDocumentSnapshot snapshot : Objects.requireNonNull(task1.getResult())) {
                                                        Product product = snapshot.toObject(Product.class);
                                                        Log.d(TAG, "All products: " + product.getName());
                                                    }
                                                }
                                        })
                                        .addOnFailureListener(e -> Log.e(TAG, "Error: ", e));
                            }
                    }
                })
                .addOnFailureListener(e -> {
                    vars.verityApp.crashlytics.log("Error while fetching categories");
                    Log.e(TAG, "Error occured: ", e);
                    vars.verityApp.crashlytics.recordException(e);
                });
    }

Maybe task1 isn't complete yet when that code reaches and because task it is actually complete, it fires anyway

Benito-B
  • 101
  • 4