Florian Walther
Florian Walther

Reputation: 6971

Firestore Pagination

I have a question about how to properly paginate queries with Firestore.

By putting the next query into the OnSuccessListener of the previous query, like in the example on the Firestore page, wouldn't it inevitably always trigger a chain reaction that loads all pages at once? Isn't that something we want to avoid with pagination?

// Construct query for first 25 cities, ordered by population
Query first = db.collection("cities")
        .orderBy("population")
        .limit(25);

first.get()
    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
        @Override
        public void onSuccess(QuerySnapshot documentSnapshots) {
            // ...

            // Get the last visible document
            DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
                    .get(documentSnapshots.size() -1);

            // Construct a new query starting at this document,
            // get the next 25 cities.
            Query next = db.collection("cities")
                    .orderBy("population")
                    .startAfter(lastVisible)
                    .limit(25);

            // Use the query for pagination
            // ...
        }
    });

Source: https://firebase.google.com/docs/firestore/query-data/query-cursors

This is my approach. I know in a real app I should use a RecyclerView, but I just want to test it on a TextView. I want to load 3 documents with every button click. Does it make sense to store the lastResult as a member and then check if it is not null, to see if it is the first query?

public void loadMore(View v) {
    Query query;
    if (lastResult == null) {
        query = notebookRef.orderBy("priority")
                .orderBy("title")
                .limit(3);
    } else {
        query = notebookRef.orderBy("priority")
                .orderBy("title")
                .startAfter(lastResult)
                .limit(3);
    }
    query.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
        @Override
        public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
            String data = "";

            for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
                Note note = documentSnapshot.toObject(Note.class);
                note.setDocumentId(documentSnapshot.getId());

                String documentId = note.getDocumentId();
                String title = note.getTitle();
                String description = note.getDescription();
                int priority = note.getPriority();

                data += "ID: " + documentId
                        + "\nTitle: " + title + "\nDescription: " + description
                        + "\nPriority: " + priority + "\n\n";
            }

            if (queryDocumentSnapshots.size() > 0) {
                data += "_____________\n\n";
                textViewData.append(data);


                lastResult = queryDocumentSnapshots.getDocuments()
                        .get(queryDocumentSnapshots.size() - 1);
            }
        }
    });
}

Upvotes: 2

Views: 4208

Answers (2)

Rohit Maurya
Rohit Maurya

Reputation: 750

You have to use ScrollListener in you recyclerview/listview. on start you are fetching 25 data limit, once user scroll to end of the page again you have to make new firestore call with limit(whatever you are keeping). but at this time you have to keep use startAt() in your query. input to startAt() will be your last key from the first fetched data.Its just basic overview. You can refer this link for query.

You can create pagination in recyclerview/listview with firestore as follow:

Basically Follow these steps:

1) On opening Activity/Fragment your first query will fetch 25 data limit

Query first = db.collection("cities")
        .orderBy("population")
        .limit(25);

first.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
        @Override
        public void onSuccess(QuerySnapshot documentSnapshots) {
            // add data to recyclerView/listview

            // Get the last visible document
            DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
                    .get(documentSnapshots.size() -1);
        }
    });

2) Now overwrite onScrollListener of adapter

boolean isEndChildResults = false;
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                        isScrolling = true;
                    }
                }
   @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            currentVisibleItem = linearLayoutManager.getChildCount();
            totalItem = linearLayoutManager.getItemCount();
            scrolledItem = linearLayoutManager.findFirstVisibleItemPosition();
            if (isScrolling && (currentVisibleItem + scrolledItem == totalItem) && !isEndChildResults && documentSnapshot != null) {
                isScrolling = false;
                mProgressBarScroll.setVisibility(View.VISIBLE);

                FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance();

                Query query = firebaseFirestore.collection(...).document(...).limit(25).orderBy(...).startAt(lastVisible);
                query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {

                        if (task.isSuccessful()) {
                          // add data to recyclerView/listview
                          lastVisible = documentSnapshots.getDocuments().get(documentSnapshots.size() -1);


                     if (task.getResult().size() < postPerPageLimit) {
                       // if your result size is less than your query size which means all the result has been displayed and there is no any other data to display 
                                    isEndChildResults = true;
                                }
                            }

                        }
                    }
                });

          if(isEndChildResults){
         // show snackbar/toast
            }
         }

*lastVisible documentSnapshot will change on each scroll and it will fetch data from lastVisible snapshot

Upvotes: 4

Frank van Puffelen
Frank van Puffelen

Reputation: 600016

Constructing a query does not yet read data from that query.

So this code merely creates a query:

Query next = db.collection("cities")
        .orderBy("population")
        .startAfter(lastVisible)
        .limit(25);

It does not read any data from the database. This means it also doesn't fire any onSuccess method yet.

If you immediately next.get().addOnSuccessListener(... you'd indeed create a loop that loads all page.

Upvotes: 1

Related Questions