Pagination Android Tutorial with RecyclerView: Getting Started
Pagination (Endless Scrolling or Infinite Scrolling) is a feature common in content-heavy apps. It breaks down a list of content into equal smaller pieces, loaded one at a time.
This is the first post in a series of Pagination articles. The series covers how to implement Pagination with RecyclerView, handle adapter changes with new data, error handling and more.
This article has been updated for AndroidX!
Pagination Series Overview
- Pagination Android Tutorial: Getting Started
- Using APIs with Retrofit and Gson
- Error Handling
- Using Multiple RecyclerView Types
- Adding Swipe-to-Refresh Support
The What, Why and When of Pagination
Pagination is the process of dividing a document into discrete pages, either electronic pages or printed pages. – Wikipedia
Already know what Pagination is? Jump straight to the code section!
What we’re going to do, relates to this definition. Loading the next set of data (next page), by specifying its index (page number).
What is Pagination?
Users of Facebook, Twitter or Instagram will know what I’m talking about. After all, we’ve spent countless hours scrolling through them haven’t we? They just don’t seem to end!
Why Pagination?
From a developer’s perspective, how would you load all of that content? It is not possible to make a call for such huge content at one go. We’ll have to request them in parts, or ‘pages’.
Pagination allows the user to see the latest content with little wait time. As we load the next ‘page’ by the time users scroll to the bottom, more content is loaded and available.
When to use Pagination?
I’m sure you have a pretty good idea by now on when to use it. If you have a ton of content that takes too long to load. This can be either from a local database or an API call. Then it makes sense to use Pagination. If you’re pulling from a database, request data in batches (say 20 per request). The same also holds true for an API call.
TIP
Good APIs that deal with a ton of content, do provide Pagination support.
With that out of the way, I’m sure you’d want to see some Android code with Pagination in action. So without further delay, let’s get straight to it!
Getting Started
As we all know, Pagination has been around for quite some time. This opens it up to various approaches on how exactly to do it.
Here are some solutions you might have come across:
- Endless Scrolling with AdapterView and
RecyclerView
– guides.codepath.com - How to implement an endless list with
RecyclerView
– stackoverflow.com
But these don’t tell you how to show or hide a footer loading progress (ProgressBar
). You also would have resorted to an Android Pagination library on GitHub:
Usually, I’d suggest one of the above, but this time I won’t. That’s because we can roll our own scroll listener. This would be simpler instead. Sometimes it’s better to be in control of our feature, especially when third party solutions refuse to work the way you want.
Android Pagination with RecyclerView
Custom OnScrollListener
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
LinearLayoutManager layoutManager;
public PaginationScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >=
totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
protected abstract void loadMoreItems();
public abstract int getTotalPageCount();
public abstract boolean isLastPage();
public abstract boolean isLoading();
}
Copy over this class. To enable Pagination, we must detect the user reaching the end of the list (RecyclerView
). PaginationScrollListener allows us to do so.
NOTE:
The onScrolled()
logic is the most important piece of your entire Pagination logic. So make sure you’re doing it right. The key snippet which contains the Pagination logic is as follows.
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >=
totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
Layout Setup
Create a layout with RecyclerView
and a ProgressBar
(for indicating load of initial content).
// activity_main.xml skeleton layout
<FrameLayout>
<androidx.recyclerview.widget.RecyclerView />
<ProgressBar android:layout_gravity="center”/>
</FrameLayout>
Creating RecyclerView.Adapter
First, create class PaginationAdapter extending RecyclerView.Adapter
, and then create two RecyclerView.ViewHolder
.
- class
ContentVH
(main content item) - class
LoadingVH
(footer ProgressBar used for Pagination)
I will be going through the essentials of the adapter, as creating a regular RecyclerView.Adapter
is quite common by now.
public class PaginationAdapter extends RecyclerView.Adapter < RecyclerView.ViewHolder > {
// flag for footer ProgressBar (i.e. last item of list)
private boolean isLoadingAdded = false;
...
@Override
public int getItemCount() {
return movies == null ? 0 : movies.size();
}
@Override
public int getItemViewType(int position) {
return (position == movies.size() - 1 && isLoadingAdded) ? LOADING : ITEM;
}
...
}
For our example, let’s assume we want to display a list of movies.
NOTEList<Movies>
(your data) must reside in the RecyclerView.Adapter
class only. You can access it via Getter Setters.
Adapter Helper Methods
Add the following methods to PaginationAdapter. They will be useful for added data fetched via Pagination.
public void add(Movie mc) {
movies.add(mc);
notifyItemInserted(movies.size() - 1);
}
public void addAll(List < Movie > mcList) {
for (Movie mc: mcList) {
add(mc);
}
}
public void remove(Movie city) {
int position = movies.indexOf(city);
if (position > -1) {
movies.remove(position);
notifyItemRemoved(position);
}
}
public void clear() {
isLoadingAdded = false;
while (getItemCount() > 0) {
remove(getItem(0));
}
}
public boolean isEmpty() {
return getItemCount() == 0;
}
public void addLoadingFooter() {
isLoadingAdded = true;
add(new Movie());
}
public void removeLoadingFooter() {
isLoadingAdded = false;
int position = movies.size() - 1;
Movie item = getItem(position);
if (item != null) {
movies.remove(position);
notifyItemRemoved(position);
}
}
public Movie getItem(int position) {
return movies.get(position);
}
That is all for the adapter. Complete PaginationAdapter code is available here, just in case.
Preparing RecyclerView
// Setup Layout Manager
linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
// Setup Adapter
PaginationAdapter adapter = new PaginationAdapter(this);
recyclerView.setAdapter(adapter);
Upon careful inspection, Pagination works in this flow:
- Display loading progress (
ProgressDialog
) on the empty screen while fetching initial data - Hide
ProgressDialog
and display data - Detect user scroll to the end of the list
- Show
ProgressDialog
at footer while fetching next page data - Remove footer
ProgressDialog
and display fetched data - Repeat Steps 3, 4 & 5 until all pages have loaded
Notice that the setup for certain steps is already in place. Loading initial data (page 0) will handle Step 1 and 2. PaginationListener will handle Step 3 and 6. PaginationAdapter helper methods will handle Step 4 and 5.
The following code setup will show you how all this will tie together.
Activity Setup
Here’s the entire MainActivity.java. It should give you a clear understanding of how everything ties together.
Be sure to note the variables here. As each plays a key part in the Pagination logic. Also, remember that TOTAL_PAGES
will be determined by how many pages your API has. Usually, this will be included in the paginated response.
public class MainActivity extends AppCompatActivity {
PaginationAdapter adapter;
LinearLayoutManager linearLayoutManager;
RecyclerView rv;
ProgressBar progressBar;
// Index from which pagination should start (0 is 1st page in our case)
private static final int PAGE_START = 0;
// Indicates if footer ProgressBar is shown (i.e. next page is loading)
private boolean isLoading = false;
// If current page is the last page (Pagination will stop after this page load)
private boolean isLastPage = false;
// total no. of pages to load. Initial load is page 0, after which 2 more pages will load.
private int TOTAL_PAGES = 3;
// indicates the current page which Pagination is fetching.
private int currentPage = PAGE_START;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
// init views
adapter = new PaginationAdapter(this);
linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
rv.setLayoutManager(linearLayoutManager);
rv.setAdapter(adapter);
rv.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
@Override
protected void loadMoreItems() {
isLoading = true;
//Increment page index to load the next one
currentPage += 1;
loadNextPage();
}
@Override
public int getTotalPageCount() {
return TOTAL_PAGES;
}
@Override
public boolean isLastPage() {
return isLastPage;
}
@Override
public boolean isLoading() {
return isLoading;
}
});
loadFirstPage();
}
...
}
Loading Initial Data
Here’s how we’ll be using that method to perform the initial load (i.e. 1st-page request):
private void loadFirstPage() {
// fetching dummy data
List < Movie > movies = Movie.createMovies(adapter.getItemCount());
progressBar.setVisibility(View.GONE);
adapter.addAll(movies);
if (currentPage <= TOTAL_PAGES) adapter.addLoadingFooter();
else isLastPage = true;
}
We can even mimic network delay, using a Handler.
// mocking 1 second network delay
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
loadFirstPage();
}
}, 1000);
Once we load the initial request and get data, hide the ProgressBar
. Next, the fetched data is added to the adapter and notified. The addAll()
helper method in PaginationAdapter accomplishes this.
NOTE
The initial data to be loaded is the 1st page (0th index). This must be a separate call, as the remaining pages are handled differently.
Using PaginationScrollListener
Notice how PaginationScrollListener uses our Activity
defined flags. It needs the LayoutManager supplied to RecyclerView
to count and compare the number of items it has. This is more accurate to know how many items are actually in layout, rather than counting the List<Model>. But for now, its constructor only supports LinearLayoutManager
.
Once initial data has loaded, its time to listen to scroll changes and trigger the next page.
private void loadNextPage() {
List < Movie > movies = Movie.createMovies(adapter.getItemCount()); // 1
adapter.removeLoadingFooter(); // 2
isLoading = false; // 3
adapter.addAll(movies); // 4
if (currentPage != TOTAL_PAGES) adapter.addLoadingFooter(); // 5
else isLastPage = true;
}
Now let’s look at the steps we take to load page 2. This remains the same for all pages beyond this.
- Get data (response) from API
- Remove footer
ProgressBar
to make way for new data - Indicate that the next page is currently not loading (using
isLoading
) - Add your new data to the adapter
- Check if this is the last page. If not, add back the footer
ProgressBar
Final Output
As per our code setup, we create 10 dummy content per page load. The number of times Pagination will happen is 3 (TOTAL_PAGE
). Add that with the initial page load, and you’re looking at 40 items in total.
Go ahead and run the app.
For Scroll listener, refer to this updated class – PaginationScrollListener.
Great job there! We now have a working Android app with a RecyclerView
that supports Pagination.
What’s next?
In this article, we learned the logic behind paginating our data. However, this is just some dummy data. In the real world, we get data from an API. So with network calls in play, how would you paginate?
You can read about this in Part 2 – Pagination with real data via APIs using Retrofit.
So how are you going to Paginate your apps? Do you think this approach is good? Or do you have something better in mind? Drop ’em in the comments below.
Product Designer who occasionally writes code.
Thank you for the tutorial
There is an existing class called Movie in android.graphics whic is deprecated now which I noticed when working with your tutorial. Although we are defining our own class, would it not be a good idea to avoid classnames that are already existent?
Thanks for highlighting this. Yes, that definitely is a better approach.
Great work man !!
I wanted to ask one thing, Whats the purpose of this condition in PaginationScrollListener.java ??
totalItemCount >= getTotalPageCount()
You’ve put it in the code but not in the code on this page ..
Hi Osama,
Good eye. Yes, that’s something I haven’t included. However, the article is now updated. Thank you for pointing it out.
Nice tutorial.. Dear Can you please guide If we don’t know how many pages are there i.e. TOTAL_PAGES. Also we are not sure about no of items on pages i.e. page 1 contains 15 items and in some other api page 1 contains 25 items. Thanks in Advance.
Nice Tutorial….Do you have any tute for play all youtube videos from a you tube channl using API3 with pagination.Thank you…
Hi, @suleiman19:disqus, how do you manage orientation change? I mean, should I save the current state and data of the RecyclerView? If that’s the case, how does it affect your scrolllistener and currentPage variable?
Hi,
You will have to save state for your data and pagination flags. Otherwise, an orientation change will trigger loading back from page 1.
I’d love to read this article, but this page is so damn slow. I cannot scroll without lagging. It’s annoying because I like this tutorial
Hi,
Thanks for letting me know. Its been fixed now.
Just clear your cache and reload the page. It should work fine.
It works now. Thanks for the hard work. I assume it’s because of the sidebar for sharing the article that caused this? I am still studying this code since I am creating a very similar app but with buy and sell app (like ebay).
I write my own php code to get the database record. Can you suggest a way to write an sql code to get items since I write my sql with ‘LIMIT’ function. :/
Hey,
It was indeed the side bar that was causing it. However I have no clue about PHP so I’m afraid I can’t help you there, sorry.
It’s fine. I have managed to fix this and it is now looking great especially with the error handling. 😀 Are you going to write about pull to refresh too?
Yeah, Pull to Refresh is in my todo list. Will be writing about it soon 🙂
Anyway, I kind of need help regarding the TOTAL_PAGE. If I increased it, it stops working altogether. Is there any other way to just keep loading without the total page?
Hey,
There’s a slight change in the PaginationScrollListener. Please take a look at the updated file (link under source code section, end of post).
@suleiman to follow up with your previous FastAdapter blog post. The FastAdapter extensions also include some useful functions and helper classes for this. There is a full featured sample activity available: https://github.com/mikepenz/FastAdapter/blob/develop/app/src/main/java/com/mikepenz/fastadapter/app/EndlessScrollListActivity.java 😉
But it is always great to show people how do these things with plain android 😉
Hi Mike,
Thanks for the tip. Your library would always be my go to solution, had I remembered it when in need. However, like you said, sometimes plain android can get it done faster. Especially when people need time to familiarize with a new library 🙂