Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to paginate queries by combining query cursors using FirestoreRecyclerAdapter?

I have a query that looks like this:

Query first = ref.orderBy("name", Query.Direction.ASCENDING).limit(10);

This is how I display the data to my RecyclerView.

firestoreRecyclerOptions = new FirestoreRecyclerOptions.Builder<ModelClass>().setQuery(query, ModelClass.class).build();
myFirestoreRecyclerAdapter = new MyFirestoreRecyclerAdapter(firestoreRecyclerOptions);
recyclerView.setAdapter(myFirestoreRecyclerAdapter);

I'm trying to use pagination as specified in here and I cannot solve it.

first.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
    @Override
    public void onSuccess(QuerySnapshot documentSnapshots) {
        DocumentSnapshot lastVisible = documentSnapshots.getDocuments().get(documentSnapshots.size() - 1);
        Query second = ref.orderBy("name", Query.Direction.ASCENDING).startAfter(lastVisible).limit(10);
        //Create a firestoreRecyclerOptions and setting the adapter
    }
});

Is there a way to paginate queries by combining query cursors using FirestoreRecyclerAdapter? Is this even possible?

like image 945
Joan P. Avatar asked May 29 '18 20:05

Joan P.


People also ask

What is firestore pagination?

Pagination is the process of dividing data into discrete pages. In Firestore, it is achieved by ordering a collection by a field, limiting it to a consistent page size, then offsetting the query.

How do you filter data in firebase realtime database?

We can filter data in one of three ways: by child key, by key, or by value. A query starts with one of these parameters, and then must be combined with one or more of the following parameters: startAt , endAt , limitToFirst , limitToLast , or equalTo .

How much data can firebase handle?

The total data in each write operation should be less than 256 MB. Multi-path updates are subject to the same size limitation.


2 Answers

As @FrankvanPuffelen already answered in an earlier question of yours, you cannot achieve that because in your case, you should pass 2 different queries (first and second) to a single adapter, which is not possible with FirestoreRecyclerAdapter. You can either use the first query or the second with a single instance of your adapter.

A solution would be to create two different lists, containing the results from each query and combine them. Then you can pass the resulting list to another adapter, let's say an ArrayAdapter and then display the results into a ListView or even better in a RecyclerView. The problem in this case is that you will not be able to use the real-time features that the FirestoreRecyclerAdapter class provides but this approach will solve your problem.

Edit:

According to your request from the comment section, I'll give you an example on how to paginate a query on button click in the easiest way, using a ListView and an ArrayAdapter. You can achieve the same thing also using a RecyclerView when scrolling down. But to keep things simple, let's assume we have a ListView and a Button and we want to load more items to the list on every button click. For that, let's define first the views :

ListView listView = findViewById(R.id.list_view);
Button button = findViewById(R.id.button);

Let's assume we have a database structure that looks like this:

Firestore-root
   |
   --- products (collection)
         |
         --- productId (document)
                |
                --- productName: "Product Name"

And a model class that looks like this:

public class ProductModel {
    private String productName;

    public ProductModel() {}

    public ProductModel(String productName) {this.productName = productName;}

    public String getProductName() {return productName;}

    @Override
    public String toString() { return productName; }
}

Now, let's define a query with the limit set to 3.

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference productsRef = rootRef.collection("products");
Query firstQuery = productsRef.orderBy("productName", Query.Direction.ASCENDING).limit(3);

This means that on every button click, we'll load 3 more items. And now, here is the code that does the magic:

firstQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        if (task.isSuccessful()) {
            List<ProductModel> list = new ArrayList<>();
            for (DocumentSnapshot document : task.getResult()) {
                ProductModel productModel = document.toObject(ProductModel.class);
                list.add(productModel);
            }
            ArrayAdapter<ProductModel> arrayAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, list);
            listView.setAdapter(arrayAdapter);
            lastVisible = task.getResult().getDocuments().get(task.getResult().size() - 1);

            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Query nextQuery = productsRef.orderBy("productName", Query.Direction.ASCENDING).startAfter(lastVisible).limit(3);
                    nextQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                        @Override
                        public void onComplete(@NonNull Task<QuerySnapshot> t) {
                            if (t.isSuccessful()) {
                                for (DocumentSnapshot d : t.getResult()) {
                                    ProductModel productModel = d.toObject(ProductModel.class);
                                    list.add(productModel);
                                }
                                arrayAdapter.notifyDataSetChanged();
                                lastVisible = t.getResult().getDocuments().get(t.getResult().size() - 1);
                            }
                        }
                    });
                }
            });
        }
    }
});

In which lastVisible is a DocumentSnapshot object that represents the last visibile item from the query. In this case, every third one and it is declared as gloabl variable:

private DocumentSnapshot lastVisible;

Edit2: Here you have also the solution on how you can get the data from your Firestore database and display it in smaller chunks in a RecyclerView when user scrolls.

like image 177
Alex Mamo Avatar answered Nov 17 '22 00:11

Alex Mamo


A possible approach is save the push keys of each item in a separate node.

Then fetch all keys from that node to an array list or something ..

Then based on your pagination number fetch the keys from this array List and use orderByKey Query and startAt and LimitToFirst queries combined to make the pagination algorithm

like image 38
Jinson Paul Avatar answered Nov 16 '22 23:11

Jinson Paul