Paging 3 helps to load the collection of data from the server, local storage, or both. This library is part of Android Jetpack and seamlessly integrates with other components. Paging 3 helps you to reduce network requests that will reduce bandwidth usages. It supports Kotlin coroutines and Flow, as well as LiveData and RxJava. It also provides support for retry, error handling, and refresh are out of the box features. In this article, we will implement Paging 3 Android Example in Java language.

In this example, we will build a movies application. This application will load movies from API and display them in a Recyclerview. Furthermore, we will use all the latest Jetpack library components. You can download the source code and demo application from the following links.


Paging 3 Android Example in Java - Building Movies Application |

Paging 3 Android Example – Building Movies Application

We will use The Movie Database API to get popular movies. The application contains a list of movies. Movies will be shown using a RecyclerView. I will try to explain each part of the code at the beginner level.

1. Application architecture

I am following the recommended Android app architecture for building the application. There are four layers to the application.

  • Remote Data Source layer
  • Repository layer
  • ViewModel layer
  • UI layer
Paging 3 integration architecture in movies application
Paging 3 integration architecture in movies application

Remote Data Source layer

The component in the Remote Data Source layer is Retrofit. That will call The Movie Database API. Results will be exposed to PagingSource.

Repository layer

PagingSource provides logic to retrieve information from an API or local database. It loads the data page by page. PagingSource object can load information single data source.

ViewModel layer

Pager provides API to construct Paging Data objects exposed by Paging Source. It queries a PagingSource object and stores the result. PagingData contains data of a page or an error.

UI layer

The primary component of paging in the UI layer is PagingDataAdapter. A RecyclerView adapter that handles paginated data. We have to subscribe to Flow<PagingData> in the UI layer.

Furthermore, we have LoadStateAdapter in the UI layer. It handles the load state of the Paging. There are three LoadState.

  • If there is no active load operation and no errors, then LoadState is a LoadState.NotLoading object.
  • If there is an active load operation, then LoadState is a LoadState.Loading object.
  • LoadState is a LoadState.Error object If there is an error while loading.

2. Setting up dependencies

This application is build using modern android architecture components for example ViewModel, Lifecycle, and ViewBinding. For calling API we are using Retrofit and okhttp3 libraries. Furthermore, for converting JSON response to java object Gson library is used. Add the following code to app-level build.gradle file.

  • Enable VIewBinding
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"
    android.buildFeatures.viewBinding true

Note : Rebuild project to generate ViewBinding classes.

  • Enable Java 8
compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
  • Add Libraries
dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation ''
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    // Retrofit library dependency
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    // Gson Library dependency for json to object conversion
    implementation ''
    // Gson support adapter for Retrofit
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

    def paging_version = "3.0.0-alpha10"
    implementation "androidx.paging:paging-runtime-ktx:$paging_version"
    // RxJava3 support for paging library
    implementation "androidx.paging:paging-rxjava3:$paging_version"
    // Retrofit support adapter for RxJava
    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
    // Optional - Okhttp logging library for debug perpose
    implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
    // Image loading library support
    implementation 'com.squareup.picasso:picasso:2.71828'

3. Create model classes

API returns JSON response. This response needs to be converted to an equivalent Java object. Create new class Model > These classes are created using jsonschema2pojo.

    POJO class to contain movie JSON details
public class Movie {
    private Integer id;
    private String posterPath;
    private Double voteAverage;

    public Integer getId() {
        return id;

    public void setId(Integer id) { = id;

    public String getPosterPath() {
        return posterPath;

    public void setPosterPath(String posterPath) {
        this.posterPath = posterPath;

    public Double getVoteAverage() {
        return voteAverage;

    public void setVoteAverage(Double voteAverage) {
        this.voteAverage = voteAverage;

    public boolean equals(Object other) {
        if (other == null) return false;
        if (other == this) return true;
        return false;

Create another class Model >


import java.util.List;

    POJO class for to contain movie response json details
public class MovieResponse {
    private Integer page;
    private List<Movie> movies = null;
    private Integer totalPages;
    private Integer totalResults;

    public Integer getPage() {
        return page;

    public void setPage(Integer page) { = page;

    public List<Movie> getResults() {
        return movies;

    public void setResults(List<Movie> movies) {
        this.movies = movies;

    public Integer getTotalPages() {
        return totalPages;

    public void setTotalPages(Integer totalPages) {
        this.totalPages = totalPages;

    public Integer getTotalResults() {
        return totalResults;

    public void setTotalResults(Integer totalResults) {
        this.totalResults = totalResults;

4. Create API Client

To make HttpRequest we are using Retrofit and OkHttpClient. Firstly we need API Key to use The Movie Database API.

  1. Create an account at The Movie Database.
  2. Go to Settings > API > New API Key. Create a new API Key and keep it with you.

Create new class API > Enter your API key in the query parameter placeholder in the below class.

import com.loopwiki.movieground.Model.MovieResponse;
import io.reactivex.rxjava3.core.Single;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Query;

    This class will provide us a single instance of Retrofit
public class APIClient {
    // Define APIInterface
    static APIInterface apiInterface;

    // Method to get APIInterface
    public static APIInterface getAPIInterface() {

        // Check for null
        if (apiInterface == null) {

            // Optional - Setup Http logging for debug purpose
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            // Create OkHttp Client
            OkHttpClient.Builder client = new OkHttpClient.Builder();
            // Set logging
            // Add request interceptor to add API key as query string parameter to each request
            client.addInterceptor(chain -> {
                Request original = chain.request();
                HttpUrl originalHttpUrl = original.url();
                HttpUrl url = originalHttpUrl.newBuilder()
                        // Add API Key as query string parameter
                        .addQueryParameter("api_key", "ENTER_YOUR_API_KEY_HERE")
                Request.Builder requestBuilder = original.newBuilder()

                Request request =;
                return chain.proceed(request);
            // Create retrofit instance
            Retrofit retrofit = new Retrofit.Builder()

                    // set base url

                    // Add Gson converter

                    // Add RxJava support for Retrofit

            // Init APIInterface
            apiInterface = retrofit.create(APIInterface.class);
        return apiInterface;

        Define API request calls in this interface
    public interface APIInterface {
        // Define Get request with query string parameter as page number
        Single<MovieResponse> getMoviesByPage(@Query("page") int page);

5. Implement PagingSource

PagingSource contains data retrieval logic. Here make a Retrofit request passing page number. We will use RxPagingSource which loads data from only one source. Create new class Paging >

import androidx.paging.rxjava3.RxPagingSource;
import com.loopwiki.movieground.API.APIClient;
import com.loopwiki.movieground.Model.Movie;
import com.loopwiki.movieground.Model.MovieResponse;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;

public class MoviePagingSource extends RxPagingSource<Integer, Movie> {

    public Single<LoadResult<Integer, Movie>> loadSingle(@NotNull LoadParams<Integer> loadParams) {
        try {
            // If page number is already there then init page variable with it otherwise we are loading fist page
            int page = loadParams.getKey() != null ? loadParams.getKey() : 1;
            // Send request to server with page number
            return APIClient.getAPIInterface()
                    // Subscribe the result
                    // Map result top List of movies
                    // Map result to LoadResult Object
                    .map(movies -> toLoadResult(movies, page))
                    // when error is there return error
        } catch (Exception e) {
            // Request ran into error return error
            return Single.just(new LoadResult.Error(e));

    // Method to map Movies to LoadResult object
    private LoadResult<Integer, Movie> toLoadResult(List<Movie> movies, int page) {
        return new LoadResult.Page(movies, page == 1 ? null : page - 1, page + 1);

6. Define a RecyclerView adapter

Paging Library provides PagingDataAdapter for creating RecyclerView adapter.

Before Creating Adapter we need to create the layout of a single movie item. Create a new layout movie_item.xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android=""

        android:src="@drawable/ic_image" />

        android:layout_height="280dp" />

        android:textColor="@color/white" />

Create new class Adapter > extending PagingDataAdapter class as shown below.

import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.paging.PagingDataAdapter;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.loopwiki.movieground.Model.Movie;
import com.loopwiki.movieground.databinding.MovieItemBinding;
import com.squareup.picasso.Picasso;
import org.jetbrains.annotations.NotNull;

    This adapter will handle listing of movies in recyclerview
public class MoviesAdapter extends PagingDataAdapter<Movie, MoviesAdapter.MovieViewHolder> {
    // Define Loading ViewType
    public static final int LOADING_ITEM = 0;
    // Define Movie ViewType
    public static final int MOVIE_ITEM = 1;

    public MoviesAdapter(@NotNull DiffUtil.ItemCallback<Movie> diffCallback) {

    public MovieViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // Return MovieViewHolder
        return new MovieViewHolder(MovieItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));

    public void onBindViewHolder(@NonNull MovieViewHolder holder, int position) {
        // Get current movie
        Movie currentMovie = getItem(position);
        // Check for null
        if (currentMovie != null) {
            // Set Image of Movie using Picasso Library
            Picasso.get().load("" + currentMovie.getPosterPath())

            // Set rating of movie

    public int getItemViewType(int position) {
        // set ViewType
        return position == getItemCount() ? MOVIE_ITEM : LOADING_ITEM;

    public class MovieViewHolder extends RecyclerView.ViewHolder {
        // Define movie_item layout view binding
        MovieItemBinding movieItemBinding;

        public MovieViewHolder(@NonNull MovieItemBinding movieItemBinding) {
            // init binding
            this.movieItemBinding = movieItemBinding;

Now one last thing is to create a Comparator. It will avoid duplication of items in RecyclerView. Create new class Util >

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import com.loopwiki.movieground.Model.Movie;
    Comparator for comparing Movie object to avoid duplicates
public class MovieComparator extends DiffUtil.ItemCallback<Movie> {
    public boolean areItemsTheSame(@NonNull Movie oldItem, @NonNull Movie newItem) {
        return oldItem.getId().equals(newItem.getId());

    public boolean areContentsTheSame(@NonNull Movie oldItem, @NonNull Movie newItem) {
        return oldItem.getId().equals(newItem.getId());

7. Handle loading, errors and retry using Paging 3

While loading page data we will show a loading view. If there is an error while loading then we will show an error message and a retry button. It can be achieved by using LoadStateAdapter. Firstly let’s create a layout for the load item load_state_item.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=""

        android:layout_gravity="center" />

        android:layout_gravity="center" />

        android:text="Retry" />


Create new class Adapter >

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.paging.LoadState;
import androidx.recyclerview.widget.RecyclerView;
import com.loopwiki.movieground.R;
import com.loopwiki.movieground.databinding.LoadStateItemBinding;
import org.jetbrains.annotations.NotNull;

public class MoviesLoadStateAdapter extends androidx.paging.LoadStateAdapter<MoviesLoadStateAdapter.LoadStateViewHolder> {
    // Define Retry Callback
    private View.OnClickListener mRetryCallback;

    public MoviesLoadStateAdapter(View.OnClickListener retryCallback) {
        // Init Retry Callback
        mRetryCallback = retryCallback;

    public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
                                                  @NotNull LoadState loadState) {
        // Return new LoadStateViewHolder object
        return new LoadStateViewHolder(parent, mRetryCallback);

    public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
                                 @NotNull LoadState loadState) {
        // Call Bind Method to bind visibility  of views

    public static class LoadStateViewHolder extends RecyclerView.ViewHolder {
        // Define Progress bar
        private ProgressBar mProgressBar;
        // Define error TextView
        private TextView mErrorMsg;
        // Define Retry button
        private Button mRetry;

                @NonNull ViewGroup parent,
                @NonNull View.OnClickListener retryCallback) {
                    .inflate(R.layout.load_state_item, parent, false));
            LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
            mProgressBar = binding.progressBar;
            mErrorMsg = binding.errorMsg;
            mRetry = binding.retryButton;

        public void bind(LoadState loadState) {
            // Check load state
            if (loadState instanceof LoadState.Error) {
                // Get the error
                LoadState.Error loadStateError = (LoadState.Error) loadState;
                // Set text of Error message
            // set visibility of widgets based on LoadState
            mProgressBar.setVisibility(loadState instanceof LoadState.Loading
                    ? View.VISIBLE : View.GONE);
            mRetry.setVisibility(loadState instanceof LoadState.Error
                    ? View.VISIBLE : View.GONE);
            mErrorMsg.setVisibility(loadState instanceof LoadState.Error
                    ? View.VISIBLE : View.GONE);

8. Add spacing in RecyclerView Items

In this example, each row of RecyclerView will have two movie items. In between each movie item we have to add space. To add this space we have to create ItemDecoration class. Create a new class Util >

import android.view.View;

import androidx.recyclerview.widget.RecyclerView;

    This decoration class will add space in between recyclerview items
public class GridSpace extends RecyclerView.ItemDecoration {
    private int spanCount;
    private int spacing;
    private boolean includeEdge;

    public GridSpace(int spanCount, int spacing, boolean includeEdge) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;

    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view); // item position
        int column = position % spanCount; // item column

        if (includeEdge) {
            outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
            outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

            if (position < spanCount) { // top edge
       = spacing;
            outRect.bottom = spacing; // item bottom
        } else {
            outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
            outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
            if (position >= spanCount) {
       = spacing; // item top

9. Set up a stream of PagingData

According to modern android architecture, we will add our stream of PagingData in ViewModel. Pager class provides us PagingData stream from PagingSource. PagingConfig class provides Pager Paging options. Paging library supports multiple data streams like Flowable, Observable as well as Flow and LiveData. In this application, we will use Flowable from the RxJava library. Create a new class ViewModel >

import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelKt;
import androidx.paging.Pager;
import androidx.paging.PagingConfig;
import androidx.paging.PagingData;
import androidx.paging.rxjava3.PagingRx;
import com.loopwiki.movieground.Model.Movie;
import com.loopwiki.movieground.Paging.MoviePagingSource;
import io.reactivex.rxjava3.core.Flowable;
import kotlinx.coroutines.CoroutineScope;
    ViewModel for MainActivity
public class MainActivityViewModel extends ViewModel {
    // Define Flowable for movies
    public Flowable<PagingData<Movie>> pagingDataFlow;

    public MainActivityViewModel() {

    // Init ViewModel Data
    private void init() {
        // Define Paging Source
        MoviePagingSource moviePagingSource = new MoviePagingSource();

        // Create new Pager
        Pager<Integer, Movie> pager = new Pager(
                // Create new paging config
                new PagingConfig(20, // pageSize - Count of items in one page
                        20, // prefetchDistance - Number of items to prefetch
                        false, // enablePlaceholders - Enable placeholders for data which is not yet loaded
                        20, // initialLoadSize - Count of items to be loaded initially
                        20 * 499),// maxSize - Count of total items to be shown in recyclerview
                () -> moviePagingSource); // set paging source

        // inti Flowable
        pagingDataFlow = PagingRx.getFlowable(pager);
        CoroutineScope coroutineScope = ViewModelKt.getViewModelScope(this);
        PagingRx.cachedIn(pagingDataFlow, coroutineScope);


10. Display Paging Data in RecyclerView

Firstly, create layout for activity activity_main.xml. Layout contains RecyclerView widget.

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=""


            app:popupTheme="@style/Theme.MovieGround.PopupOverlay" />



Now that you have created PagingSource, a stream of PagingData and PagingDataAdapter. Let’s connect these parts together and display them in your activity.

  1. Create PagingDataAdapater instance passing new DiffUtil.ItemCallback<>.
  2. Initialize ViewModel.
  3. Subscribe to the stream of PagingData. Inside subscribe submit data to PagingDataAdapater upon receiving new data.
  4. Set layout manager as GridLayoutManager
  5. Add Space ItemDecoration to RecyclerView
  6. Set ConcatAdapter as RecyclerView adapter by combing MoviesAdapter with MoviesLoadStateAdapter.
  7. Finally set span size of GridLayoutManager based on ViewType.

Create new activity class Activity >

import android.os.Bundle;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import com.loopwiki.movieground.Adapter.MoviesLoadStateAdapter;
import com.loopwiki.movieground.Adapter.MoviesAdapter;
import com.loopwiki.movieground.Util.GridSpace;
import com.loopwiki.movieground.Util.MovieComparator;
import com.loopwiki.movieground.ViewModel.MainActivityViewModel;
import com.loopwiki.movieground.databinding.ActivityMainBinding;

This activity shows list of movies using paging 3 library and RecyclerView
public class MainActivity extends AppCompatActivity {

    protected void onCreate(Bundle savedInstanceState) {
        // Create View binding object
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

        // Create new MoviesAdapter object and provide
        MoviesAdapter moviesAdapter = new MoviesAdapter(new MovieComparator());

        // Create ViewModel
        MainActivityViewModel mainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);

        // Subscribe to to paging data
        mainActivityViewModel.pagingDataFlow.subscribe(moviePagingData -> {
            // submit new data to recyclerview adapter
            moviesAdapter.submitData(getLifecycle(), moviePagingData);

        // Create GridlayoutManger with span of count of 2
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);

        // Finally set LayoutManger to recyclerview

        // Add ItemDecoration to add space between recyclerview items
        binding.recyclerViewMovies.addItemDecoration(new GridSpace(2, 20, true));

        // set adapter
                // concat movies adapter with header and footer loading view
                // This will show end user a progress bar while pages are being requested from server
                        // Pass footer load state adapter.
                        // When we will scroll down and next page request will be sent
                        // while we get response form server Progress bar will show to end user
                        // If request success Progress bar will hide and next page of movies
                        // will be shown to end user or if request will fail error message and
                        // retry button will be shown to resend the request
                        new MoviesLoadStateAdapter(v -> {

        // set Grid span
        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            public int getSpanSize(int position) {
                // If progress will be shown then span size will be 1 otherwise it will be 2
                return moviesAdapter.getItemViewType(position) == MoviesAdapter.LOADING_ITEM ? 1 : 2;

11. Run the Application

Note: Add Internet permission to your mainfest.xml file. Lastly, Run the application and experience of Paging Library Magic.

Paging 3 Example Movie Application screenshot
Paging 3 Example Movie Application

12. References

  1. Paging 3 documentation
  2. The Movie Database Credentials


You can use Paging 3 Library to load data from the network, local storage, or both. Above all seamless integration of Paging in other android architecture components are just awesome.

I hope you understand Paging 3 Library integration. If you still have any queries, please post them in the comments section below, I will be happy to help you.


Hello there, My name is Amardeep founder of I have experience in many technologies like Android, Java, Php, etc. In this variety of technologies, I love Android App Development. If you have any idea and you want me to develop for you then let's have chat Conatct


  1. raekuu alaxi Reply

    thnx for this education .

    please help me to set PagingConfig maxSize from api model ?

    • I do all like you. But my fake application load only page size + prefetched size item I recycler view. Can help?

  2. Jack Black Reply

    How would you do it so that after orientation change, you’re displaying (roughly) the same movies as before?

Write A Comment