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
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
<?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.
17 Comments
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
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
verry helpful
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;
}
});
where is the item_loading xml
Paritosh post is updated please check
where is item_loading XML
will update soon
where is item_loading XML
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
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
hi
y its supporting only for linear layout manager . i am using grid layout manager now what to do , i want pagination.
hi,
don’t worry about GridLayoutManager since his parent is LinearLayoutManager it will work with so ,try it; for it works.
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);
}
}