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

  1. Pagination Android Tutorial: Getting Started
  2. Using APIs with Retrofit and Gson
  3. Error Handling
  4. Using Multiple RecyclerView Types
  5. 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!

twitter profile tweets pagination endless scroll load more
Twitter Profile Tweets list

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:

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.

  1. class ContentVH (main content item)
  2. 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.

NOTE
List<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:

  1. Display loading progress (ProgressDialog) on the empty screen while fetching initial data
  2. Hide ProgressDialog and display data
  3. Detect user scroll to the end of the list
  4. Show ProgressDialog at footer while fetching next page data
  5. Remove footer ProgressDialog and display fetched data
  6. 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.

  1. Get data (response) from API
  2. Remove footer ProgressBar to make way for new data
  3. Indicate that the next page is currently not loading (using isLoading)
  4. Add your new data to the adapter
  5. 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.

Pagination result GIF
Pagination result GIF

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.

Suleiman

Product Designer who occasionally writes code.

You may also like...

19 Responses

  1. Meena M says:

    Thank you for the tutorial

  2. Meena M says:

    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?

  3. Osama says:

    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 ..

    • Suleiman says:

      Hi Osama,
      Good eye. Yes, that’s something I haven’t included. However, the article is now updated. Thank you for pointing it out.

  4. Tushar says:

    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.

  5. mark steven says:

    Nice Tutorial….Do you have any tute for play all youtube videos from a you tube channl using API3 with pagination.Thank you…

  6. Pedro Joya says:

    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?

  7. Karlsanada13 says:

    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

    • Suleiman19 says:

      Hi,
      Thanks for letting me know. Its been fixed now.
      Just clear your cache and reload the page. It should work fine.

      • Karlsanada13 says:

        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. :/

      • Suleiman19 says:

        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.

      • Karlsanada13 says:

        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?

      • Suleiman19 says:

        Yeah, Pull to Refresh is in my todo list. Will be writing about it soon 🙂

      • Karlsanada13 says:

        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?

      • Suleiman19 says:

        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).

  8. Mike Penz says:

    @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 😉

    • Suleiman19 says:

      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 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *