Pagination Android Tutorial: 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.

Pagination Series Overview

  1. Pagination Android Tutorial: Getting Started
  2. Using APIs with Retrofit and Gson
  3. Error Handling
  4. Using Multiple RecyclerView Types (coming soon)
  5. Adding Swipe-to-Refresh Support (coming soon)

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? aThey 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 its 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
                    && totalItemCount >= getTotalPageCount()) {
                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.

Layout Setup

Create a layout with RecyclerView and a ProgressBar (for indicating load of initial content).

// activity_main.xml skeleton layout

<FrameLayout> 
    <android.support.v7.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 that the content we want to display, is 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

// RecyclerView Adapter setup
PaginationAdapter adapter = new PaginationAdapter(this);
 linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
 rv.setLayoutManager(linearLayoutManager);
 rv.setItemAnimator(new DefaultItemAnimator());
 rv.setAdapter(adapter);

Upon careful inspection, Pagination works in this flow:

  1. Display loading progress (ProgressDialog) on empty screen while fetching initial data
  2. Hide ProgressDialog and display data
  3. Detect user scroll to the end of 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 are 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);
  
        ...

        rv = (RecyclerView) findViewById(R.id.main_recycler);
        progressBar = (ProgressBar) findViewById(R.id.main_progress);

        adapter = new PaginationAdapter(this);

        linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        rv.setLayoutManager(linearLayoutManager);
        rv.setItemAnimator(new DefaultItemAnimator());
        rv.setAdapter(adapter);

        rv.addOnScrollListener(new PaginationScrollListener(linearLayoutManager) {
            @Override
            protected void loadMoreItems() {
                isLoading = true;
                currentPage += 1; //Increment page index to load the next one
             	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 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

SOURCE CODE: Available on GitHub.

We now have a working Android app with a RecyclerView that supports Pagination. What apps did you Paginate? Drop ’em in the comments below.

Suleiman

Android developer and tech enthusiast. Love creating apps with good UI UX design. I also like to sketch and listen to Chillstep.

You may also like...