Reputation: 6971
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
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
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