Android & Kotlin

Pagination in RecyclerView in Android

Pinterest LinkedIn Tumblr

In this blog, we will learn pagination in RecyclerView in android. Most apps required pagination features in own android apps. Specially, In social media app such as Twitter, Facebook, Instagram, etc. In this pagination RecyclerView android example, we will create a custom adapter set on RecyclerView same as below video.

If you are on Kotlin code base I will recommend you should use the default Android Paging Library

Introduction

In Android, Pagination is a very common feature for heavy-data apps today. It’s also called endless scrolling or infinite scrolling. You want to fetch all data of feed at once and bind with the view, which could time-consuming and your app will appear as running slow. Pagination is used for better user experience and resource management. It has broken down a list of content into equal smaller pieces, loaded one at a time.

In this tutorials, we will implement Pagination with RecyclerView, handling adapter change with new data, error handling and more.

Pagination Overview

  • Need of Pagination
  • Create and Setup Project
  • Create list item views
  • Write a RecyclerView Adapter and inflate the row item view
  • Set adapter on RecyclerView with SwipeRefreshLayout
  • The final step is set Pagination Scroll listener of RecyclerView

Need of Pagination

On a daily basis, you are used many pagination apps. eg. Facebook, Twitter, and Instagram. When you scroll this list and reaches the end of the view more items are loaded.

Why .?

Pagination allows the user to see the latest content with little wait time and for providing smooth user experience we are used Pagination

When .?

You have to too big content to load that takes a long time to load, either from pulling data from database or APIs calls. Then we should use pagination

Pagination in Android RecyclerView (Pagination Android Demo App)

1. Create and Setup Project

  • In Android Studio, Go to file menu -> Create New Project -> fill all required details and select EmptyActivity template and click on Finish
  • Open project build.gradle and add below dependency for RecyclerView, CardView, and ButterKnife.
apply plugin: 'com.android.application'

android {
  compileSdkVersion 29
  buildToolsVersion "29.0.0"
  defaultConfig {
    applicationId "com.androidwave.recyclerviewpagination"
    minSdkVersion 15
    targetSdkVersion 29
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
  compileOptions {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
  }
}

dependencies {
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  implementation 'androidx.appcompat:appcompat:1.0.2'
  implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
  implementation 'androidx.recyclerview:recyclerview:1.0.0'
  implementation 'com.google.android.material:material:1.0.0'
  implementation 'androidx.cardview:cardview:1.0.0'

  // code generator for view
  implementation "com.jakewharton:butterknife:10.1.0"
  annotationProcessor "com.jakewharton:butterknife-compiler:10.1.0"

  testImplementation 'junit:junit:4.12'
  androidTestImplementation 'androidx.test:runner:1.2.0'
  androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
  •  Android Studio is creating two layout files for each activity. For MainActivity, also created activity_main.xml. In main activity add RecyclerView along with SwipeRefreshLayout component. SwipeRefreshLayout we are using for pull to refresh functionality.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    >

  <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
      android:id="@+id/swipeRefresh"
      android:layout_width="0dp"
      android:layout_height="match_parent"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      >

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
  </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

2. Create list item views

Create an XML layout named item_post.xml inside res in layout folder. This layout file will responsible for renders a single row in recycler view which contains title, discerption and time of post.

<?xml version="1.0" encoding="utf-8"?><!-- A CardView that contains a TextView -->
<androidx.cardview.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="4dp"
    android:background="#fff"
    android:padding="8dp"
    card_view:cardCornerRadius="4dp"
    >

  <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      >


    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:text="2 min Ago "
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

    <TextView
        android:id="@+id/textViewTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:letterSpacing="-0.02"

        android:lineSpacingExtra="5sp"
        android:textColor="@color/navy"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView"
        tools:text="Zero movie review"
        />

    <TextView
        android:id="@+id/textViewDescription"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:textSize="15sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewTitle"
        tools:text="Shah Rukh Khan, Katrina Kaif and Anushka Sharma"
        />
  </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

3. Create a POJO for holding view data

Create a new POJO class for holding post item data name is PostItem.java and declare title, description and time. Also add the getter/setter methods to each variable.

package com.androidwave.recyclerviewpagination;

public class PostItem {

  private String description;

  private String time;

  private String title;

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getTime() {
    return time;
  }

  public void setTime(String time) {
    this.time = time;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }
}

4. Writing a BaseViewHolder.java class for ViewHolder RecyclerView

BaseViewHolder is a helper class, It responsible for manage multiple view holders in an easier way. I was written a separate article on Best Practices of RecyclerView in Android

package com.androidwave.recyclerviewpagination;

import android.view.View;
import androidx.recyclerview.widget.RecyclerView;

public abstract class BaseViewHolder extends RecyclerView.ViewHolder {

  private int mCurrentPosition;

  public BaseViewHolder(View itemView) {
    super(itemView);
  }

  protected abstract void clear();

  public void onBind(int position) {
    mCurrentPosition = position;
    clear();
  }

  public int getCurrentPosition() {
    return mCurrentPosition;
  }
}

5. Write a RecyclerView adapter to bind the view

Let’s, create an adapter class with name PostRecyclerAdapter.java and following setup we have to for pagination

  • Create two ViewHolder class for ProgressHolder and ViewHolder
  • Here onCreateViewHolder() method inflate two view . First views for VIEW_TYPE_NORMAL that inflate item_post.xml and second for VIEW_TYPE_LOADING is inflate item_loading.xml
  • getItemViewType() is the return type of view for each position based on this layout is inflated. if the loader is visible and item position is equal to the list size then return VIEW_TYPE_LOADING otherwise VIEW_TYPE_NORMAL )
  • Adding and Deleting items in the list by using method addItems() or clear()
  • Last and important things are adding and removing loader in an adapter.
package com.androidwave.recyclerviewpagination;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import java.util.List;

public class PostRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {
  private static final int VIEW_TYPE_LOADING = 0;
  private static final int VIEW_TYPE_NORMAL = 1;
  private boolean isLoaderVisible = false;

  private List<PostItem> mPostItems;

  public PostRecyclerAdapter(List<PostItem> postItems) {
    this.mPostItems = postItems;
  }

  @NonNull @Override
  public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    switch (viewType) {
      case VIEW_TYPE_NORMAL:
        return new ViewHolder(
            LayoutInflater.from(parent.getContext()).inflate(R.layout.item_post, parent, false));
      case VIEW_TYPE_LOADING:
        return new ProgressHolder(
            LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false));
      default:
        return null;
    }
  }

  @Override
  public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
    holder.onBind(position);
  }

  @Override
  public int getItemViewType(int position) {
    if (isLoaderVisible) {
      return position == mPostItems.size() - 1 ? VIEW_TYPE_LOADING : VIEW_TYPE_NORMAL;
    } else {
      return VIEW_TYPE_NORMAL;
    }
  }

  @Override
  public int getItemCount() {
    return mPostItems == null ? 0 : mPostItems.size();
  }

  public void addItems(List<PostItem> postItems) {
    mPostItems.addAll(postItems);
    notifyDataSetChanged();
  }

  public void addLoading() {
    isLoaderVisible = true;
    mPostItems.add(new PostItem());
    notifyItemInserted(mPostItems.size() - 1);
  }

  public void removeLoading() {
    isLoaderVisible = false;
    int position = mPostItems.size() - 1;
    PostItem item = getItem(position);
    if (item != null) {
      mPostItems.remove(position);
      notifyItemRemoved(position);
    }
  }

  public void clear() {
    mPostItems.clear();
    notifyDataSetChanged();
  }

  PostItem getItem(int position) {
    return mPostItems.get(position);
  }

  public class ViewHolder extends BaseViewHolder {
    @BindView(R.id.textViewTitle)
    TextView textViewTitle;
    @BindView(R.id.textViewDescription)
    TextView textViewDescription;

    ViewHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
    }

    protected void clear() {

    }

    public void onBind(int position) {
      super.onBind(position);
      PostItem item = mPostItems.get(position);

      textViewTitle.setText(item.getTitle());
      textViewDescription.setText(item.getDescription());
    }
  }

  public class ProgressHolder extends BaseViewHolder {
    ProgressHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
    }

    @Override
    protected void clear() {
    }
  }
}

Loading layout is item_loading.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >

  <ProgressBar
      android:id="@+id/progressBar"
      style="?android:attr/progressBarStyle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginLeft="8dp"
      android:layout_marginTop="16dp"
      android:layout_marginEnd="8dp"
      android:layout_marginRight="8dp"
      android:layout_marginBottom="16dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
</android.support.constraint.ConstraintLayout>

6. Create class name PaginationScrollListener

PaginationScrollListener is utility java class that extends RecyclerView.OnScrollListener. Now must have define PAGE_SIZE here and some useful methods like loadMoreItems(), isLastPage() and isLoading() all methods uses explain later

package com.androidwave.recyclerviewpagination;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public abstract class PaginationListener extends RecyclerView.OnScrollListener {

  public static final int PAGE_START = 1;

  @NonNull
  private LinearLayoutManager layoutManager;

  /**
   * Set scrolling threshold here (for now i'm assuming 10 item in one page)
   */
  private static final int PAGE_SIZE = 10;

  /**
   * Supporting only LinearLayoutManager for now.
   */
  public PaginationListener(@NonNull LinearLayoutManager layoutManager) {
    this.layoutManager = layoutManager;
  }

  @Override
  public void onScrolled(@NonNull 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 >= PAGE_SIZE) {
        loadMoreItems();
      }
    }
  }

  protected abstract void loadMoreItems();

  public abstract boolean isLastPage();

  public abstract boolean isLoading();
}

7. Now open MainActivity.java and do the below changes.

  • Here preparedListItem() method adds sample data to list view.
  • Initialization PostRecyclerAdapter and set on the RecyclerView
  • Add addOnScrollListener() over the RecyclerView, while user scroll the list till end loadMoreItems() method will and next page will be loaded.
  • Set setOnRefreshListener() in SwipeRefreshLayout object while user pull the RecyclerView onRefresh() methods will call.

In this example app, doApiCall() methods are not calling any API. But while in real implementation you have to fetch the data from the remote server, so put all run() method code on Success of APIs

package com.androidwave.recyclerviewpagination;

import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
import java.util.ArrayList;

import static com.androidwave.recyclerviewpagination.PaginationListener.PAGE_START;

public class MainActivity extends AppCompatActivity
    implements SwipeRefreshLayout.OnRefreshListener {

  private static final String TAG = "MainActivity";

  @BindView(R.id.recyclerView)
  RecyclerView mRecyclerView;
  @BindView(R.id.swipeRefresh)
  SwipeRefreshLayout swipeRefresh;
  private PostRecyclerAdapter adapter;
  private int currentPage = PAGE_START;
  private boolean isLastPage = false;
  private int totalPage = 10;
  private boolean isLoading = false;
  int itemCount = 0;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);

    swipeRefresh.setOnRefreshListener(this);
    mRecyclerView.setHasFixedSize(true);
    // use a linear layout manager
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(layoutManager);

    adapter = new PostRecyclerAdapter(new ArrayList<>());
    mRecyclerView.setAdapter(adapter);
    doApiCall();

    /**
     * add scroll listener while user reach in bottom load more will call
     */
    mRecyclerView.addOnScrollListener(new PaginationListener(layoutManager) {
      @Override
      protected void loadMoreItems() {
        isLoading = true;
        currentPage++;
        doApiCall();
      }

      @Override
      public boolean isLastPage() {
        return isLastPage;
      }

      @Override
      public boolean isLoading() {
        return isLoading;
      }
    });
  }

  /**
   * do api call here to fetch data from server
   * In example i'm adding data manually
   */
  private void doApiCall() {
    final ArrayList<PostItem> items = new ArrayList<>();
    new Handler().postDelayed(new Runnable() {

      @Override
      public void run() {
        for (int i = 0; i < 10; i++) {
          itemCount++;
          PostItem postItem = new PostItem();
          postItem.setTitle(getString(R.string.text_title) + itemCount);
          postItem.setDescription(getString(R.string.text_description));
          items.add(postItem);
        }
        /**
         * manage progress view
         */
        if (currentPage != PAGE_START) adapter.removeLoading();
        adapter.addItems(items);
        swipeRefresh.setRefreshing(false);

        // check weather is last page or not
        if (currentPage < totalPage) {
          adapter.addLoading();
        } else {
          isLastPage = true;
        }
        isLoading = false;
      }
    }, 1500);
  }

  @Override
  public void onRefresh() {
    itemCount = 0;
    currentPage = PAGE_START;
    isLastPage = false;
    adapter.clear();
    doApiCall();
  }
}

The final code is like that. Let’s run the project and validate your action. If all is good scrolling working perfectly.

Read our Kotlin solution with Android Paging Library

Conclusion

That Up, In this tutorial, we have learned implementation Pagination in RecyclerView. I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Keep in touch

If you have any queries, feel free to ask them in the comment section below.

Download Sample Project – Pagination RecyclerView Android Example

17 Comments

  1. Can you please help us understand that based on what all conditions are you removing/adding the progress bar view? I am getting last page with total items in that page < page count due to which progress bar view is not going and last item is not visible

  2. Yunis Rəsulzadə Reply

    if your app crashes when you scroll down, make sure you use “androidx.constraintlayout.widget.ConstraintLayout” NOT “android.support.constraint.ConstraintLayout” in item_loading.xml

  3. Hi ,
    am not getting the last 4 items from the list . but onBindViewHolder is called for all the items. i have a list of total 59 items which are segregated to 6 pages basically

    and i have a PAGE_OFFSET of 0 , 10 , 20 , 30 , 40 , 50

    but i am getting only 54 items with this code. while am unable to get the last 5 items at all.

    LAYOUT FILE –

    listener in my fragment –

    rv_khata_by_customer.addOnScrollListener(new PaginationHandler(linearLayoutManager) {
    @Override
    protected void loadMoreItems() {
    isLoading = true;
    currentPage++;
    Log.e(TAG, “loadMoreItems: ” +currentPage*totalPage);
    khataPassbookActivity.loadMore(currentPage*totalPage);
    }

    @Override
    public boolean isLastPage() {
    return isLastPage;
    }

    @Override
    public boolean isLoading() {
    return isLoading;
    }
    });

  4. Afroz Ahmad Reply

    if (!isLoading() && !isLastPage()) {
    if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
    && firstVisibleItemPosition >= 0
    && totalItemCount >= PAGE_SIZE) {
    loadMoreItems();
    }
    }

    amp showing error how to resolve that , I am beginner so please help me.

    • Somehow amp; is automatically added with & operator in code. You have to need remove it

  5. How do I retrieve data from server?
    dbRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
    for (DataSnapshot postSnapshot : snapshot.getChildren()) {
    //items.add(postSnapshot.getValue(Question.class));
    Question postItem = new Question();
    postItem.setTitle(postSnapshot.getValue(Question.class).getTitle());
    postItem.setUsername(postSnapshot.getValue(Question.class).getUsername());
    postItem.setDate(postSnapshot.getValue(Question.class).getDate());
    items.add(postItem);
    }
    }
    Nothing shows when I run this code

    • This code is not sufficient to dig the problem. Connect me over my facebook page

  6. hi
    y its supporting only for linear layout manager . i am using grid layout manager now what to do , i want pagination.

    • new soft roid

      hi,
      don’t worry about GridLayoutManager since his parent is LinearLayoutManager it will work with so ,try it; for it works.

  7. hi sir,
    i hope you will be good and nice to see that you are doing good work for junior developers like me thanks in advance
    please help i am getting error in adapter on add response “java.util.ConcurrentModificationException”

    • There is a bug in the code. Change the addAll with the below. it will solve.
      public void addAll(List list){
      for(PostItem response: list){
      mPostItems.add(response);
      }
      }

Write A Comment