UI/UX

Pagination in RecyclerView in Android

In this blog, we will learn pagination in RecyclerView in android. Mostly app required pagination feature 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.

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: Shah Rukh Khan dazzles in a strange romance "
        />

    <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="Zero movie review: 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() {
    }
  }
}

6. Create class name PaginationScrollListener

PaginationScrollListener is utility java class which 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() &amp;&amp; !isLastPage()) {
      if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
          &amp;&amp; firstVisibleItemPosition >= 0
          &amp;&amp; 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 like that. Let’s run the project and validate your action. If all good scrolling working perfectly.

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

6
Leave a Reply

2000
QWERTY

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

pallavi

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

Mujahid

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”