How to Implement Pagination in Android RecyclerView using Volley?
Last Updated :
21 Apr, 2025
Pagination is one of the most important factors which helps to reduce the loading time inside our app and increase the performance of our data which is represented in the form of Lists. In this article, we will take a look at adding pagination in our Android RecyclerView. And to get the data from API we are going to use the Volley library.
Pagination is to load data according to requirements rather than loading complete data at a time. So this helps to reduce the loading time for our data from our API as well as increase the performance of our application.
Many times there is a situation when we have to load a huge amount of the data at a time in our list view or recycler view. So if we load all the data at a time it will take some time to load the data and this will increase the loading time of our Recycler View. Pagination will provide you with support with its help of it we can load data in the form of chunks so this will prevent our recycler view from degrading its performance and the loading of the data will be faster.
What are we going to build in this article?
We will be building a simple application in which we will be displaying a list of data in our Android RecyclerView and we will be adding pagination in our RecyclerView to load our data. A sample video is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Java/Kotlin language.
Steps to Implement for Pagination in Android RecyclerView using Volley
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio.
Step 2: Add the below dependency in your build.gradle file
Below is the dependency for Volley which we will be using to get the data from API. For adding this dependency navigate to the app > Gradle Scripts > build.gradle(app) and add the below dependency in the dependencies section. We have used the Picasso dependency for image loading from the URL.
dependencies {
...
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("com.android.volley:volley:1.2.1")
}
After adding this dependency sync your project and now move towards the AndroidManifest.xml part.
Step 3: Adding permissions to the internet in the AndroidManifest.xml file
Navigate to the app > AndroidManifest.xml and add the below code to it.
<uses-permission android:name="android.permission.INTERNET" />
Step 4: Working with the activity_main.xml file
Navigate to the app > res > layout > activity_main.xml and add the below code to that file.
activity_main.xml:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!--recycler view for displaying our list of data-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/idRVUsers"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
tools:listitem="@layout/user_rv_item" />
<!--we are adding progress bar for the purpose of loading-->
<ProgressBar
android:id="@+id/idPBLoading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
</LinearLayout>
Step 5: Creating a Modal class for storing our data
For storing our data we have to create a new java class. For creating a new java class, Navigate to the app > java > your app’s package name > Right-click on it > New > Java/Kotlin class and name it as UserModal.
UserModal.java
package org.geeksforgeeks.demo;
public class UserModal {
private String firstName;
private String lastName;
private String email;
private String avatar;
// Constructor
public UserModal(String firstName, String lastName, String email, String avatar) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.avatar = avatar;
}
// Getter and Setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
}
UserModal.kt
package org.geeksforgeeks.demo
class UserModal(
var firstName: String,
var lastName: String,
var email: String,
var avatar: String
)
Step 6: Creating a layout file for each item of our RecyclerView
Navigate to the app > res > layout > Right-click on it > New > layout resource file and give the file name as user_rv_item.
user_rv_item:
user_rv_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
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="wrap_content"
android:elevation="8dp"
app:cardCornerRadius="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp">
<!--image view for displaying user image-->
<ImageView
android:id="@+id/idIVUser"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5"
app:srcCompat="@mipmap/ic_launcher" />
<!--text view for displaying first name-->
<TextView
android:id="@+id/idTVFirstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="First Name"
android:textStyle="bold"
android:textColor="@color/black"
app:layout_constraintBottom_toTopOf="@+id/idTVEmail"
app:layout_constraintStart_toEndOf="@+id/idIVUser"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<!--text view for displaying last name-->
<TextView
android:id="@+id/idTVLastName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/idTVFirstName"
android:layout_marginStart="4dp"
android:text="Last Name"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/idTVFirstName"
app:layout_constraintStart_toEndOf="@+id/idTVFirstName"
app:layout_constraintTop_toTopOf="@+id/idTVFirstName" />
<!--text view for displaying user email-->
<TextView
android:id="@+id/idTVEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/idTVLastName"
android:layout_marginTop="2dp"
android:text="Email"
android:textColor="@color/black"
android:textStyle="italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/idTVFirstName"
app:layout_constraintTop_toBottomOf="@+id/idTVFirstName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Step 7: Creating an Adapter class for setting data to our RecyclerView item
For creating a new Adapter class navigate to the app > java > your app’s package name > Right-click on it > New > Java/Kotlin class and name it as UserRVAdapter
UserRVAdapter.java
package org.geeksforgeeks.demo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
public class UserRVAdapter extends RecyclerView.Adapter<UserRVAdapter.ViewHolder> {
// variable for our array list and context.
private ArrayList<UserModal> userModalArrayList;
private Context context;
// creating a constructor.
public UserRVAdapter(ArrayList<UserModal> userModalArrayList, Context context) {
this.userModalArrayList = userModalArrayList;
this.context = context;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// inflating our layout file on below line.
View view = LayoutInflater.from(context).inflate(R.layout.user_rv_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// getting data from our array list in our modal class.
UserModal userModal = userModalArrayList.get(position);
// on below line we are setting data to our text view.
holder.firstNameTV.setText(userModal.getFirstName());
holder.lastNameTV.setText(userModal.getLastName());
holder.emailTV.setText(userModal.getEmail());
// on below line we are loading our image
// from url in our image view using glide.
Glide.with(context).load(userModal.getAvatar()).into(holder.userIV);
}
@Override
public int getItemCount() {
// returning the size of array list.
return userModalArrayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
// creating a variable for our text view and image view.
// initializing our variables.
TextView firstNameTV, lastNameTV, emailTV;
ImageView userIV;
public ViewHolder(View itemView) {
super(itemView);
firstNameTV = itemView.findViewById(R.id.idTVFirstName);
lastNameTV = itemView.findViewById(R.id.idTVLastName);
emailTV = itemView.findViewById(R.id.idTVEmail);
userIV = itemView.findViewById(R.id.idIVUser);
}
}
}
UserRVAdapter.kt
package org.geeksforgeeks.demo
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
class UserRVAdapter (
// variable for our array list and context.
private val userModalArrayList: ArrayList<UserModal>,
private val context: Context
) : RecyclerView.Adapter<UserRVAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// inflating our layout file on below line.
val view = LayoutInflater.from(context).inflate(R.layout.user_rv_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// getting data from our array list in our modal class.
val userModal = userModalArrayList[position]
// on below line we are setting data to our text view.
holder.firstNameTV.text = userModal.firstName
holder.lastNameTV.text = userModal.lastName
holder.emailTV.text = userModal.email
// on below line we are loading our image
// from url in our image view using glide.
Glide.with(context).load(userModal.avatar).into(holder.userIV)
}
override fun getItemCount(): Int {
// returning the size of array list.
return userModalArrayList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// creating a variable for our text view and image view.
// initializing our variables.
internal val firstNameTV: TextView = itemView.findViewById(R.id.idTVFirstName)
val lastNameTV: TextView = itemView.findViewById(R.id.idTVLastName)
val emailTV: TextView = itemView.findViewById(R.id.idTVEmail)
val userIV: ImageView = itemView.findViewById(R.id.idIVUser)
}
}
Step 8: Working with the MainActivity file
Go to the MainActivity file and refer to the following code. Below is the code for the MainActivity file.
MainActivity.java
package org.geeksforgeeks.demo;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.volley.Request;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
// creating variables for array list, adapter, recycler view and progress bar
private ArrayList<UserModal> userModalArrayList;
private UserRVAdapter userRVAdapter;
private RecyclerView userRV;
private ProgressBar loadingPB;
// variables for pagination
private int page = 1;
// limit as per API
private final int limit = 2;
// to prevent multiple simultaneous requests
private boolean isLoading = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// initializing the array list and views
userModalArrayList = new ArrayList<>();
userRV = findViewById(R.id.idRVUsers);
loadingPB = findViewById(R.id.idPBLoading);
// initializing adapter and setting layout
// manager and adapter to recycler view
userRVAdapter = new UserRVAdapter(userModalArrayList, this);
userRV.setLayoutManager(new LinearLayoutManager(this));
userRV.setAdapter(userRVAdapter);
// fetching the first page of data
getDataFromAPI(page);
// adding scroll listener to recycler view for pagination
userRV.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// checking if we have reached the bottom and not already loading
if (!recyclerView.canScrollVertically(1) && !isLoading && page <= limit) {
// showing progress bar, incrementing page, and fetching next page
loadingPB.setVisibility(View.VISIBLE);
page++;
getDataFromAPI(page);
}
}
});
}
private void getDataFromAPI(int page) {
// if requested page exceeds limit, show message and return
if (page > limit) {
Toast.makeText(this, "That's all the data..", Toast.LENGTH_SHORT).show();
loadingPB.setVisibility(View.GONE);
return;
}
// setting loading flag to true
isLoading = true;
// constructing API URL
String url = "https://reqres.in/api/users?page=" + page;
// creating request queue
var queue = Volley.newRequestQueue(this);
// creating JSON request
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET, url, null,
response -> {
try {
// extracting data array from JSON response
JSONArray dataArray = response.getJSONArray("data");
// looping through array and adding to model list
for (int i = 0; i < dataArray.length(); i++) {
JSONObject jsonObject = dataArray.getJSONObject(i);
userModalArrayList.add(new UserModal(
jsonObject.getString("first_name"),
jsonObject.getString("last_name"),
jsonObject.getString("email"),
jsonObject.getString("avatar")
));
}
// notifying adapter about new data
userRVAdapter.notifyDataSetChanged();
// hiding progress bar and resetting loading state
loadingPB.setVisibility(View.GONE);
isLoading = false;
} catch (JSONException e) {
e.printStackTrace();
loadingPB.setVisibility(View.GONE);
isLoading = false;
}
},
error -> {
// handling error
Toast.makeText(MainActivity.this, "Fail to get data..", Toast.LENGTH_SHORT).show();
loadingPB.setVisibility(View.GONE);
isLoading = false;
});
// adding request to queue
queue.add(jsonObjectRequest);
}
}
MainActivity.kt
package org.geeksforgeeks.demo
import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.Request
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import org.json.JSONException
class MainActivity : AppCompatActivity() {
// creating variables for array list, adapter, recycler view and progress bar
private lateinit var userModalArrayList: ArrayList<UserModal>
private lateinit var userRVAdapter: UserRVAdapter
private lateinit var userRV: RecyclerView
private lateinit var loadingPB: ProgressBar
// variables for pagination
private var page = 1
private val limit = 2 // limit as per API
private var isLoading = false // to prevent multiple simultaneous requests
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// initializing the array list and views
userModalArrayList = ArrayList()
userRV = findViewById(R.id.idRVUsers)
loadingPB = findViewById(R.id.idPBLoading)
// initializing adapter and setting layout manager and adapter to recycler view
userRVAdapter = UserRVAdapter(userModalArrayList, this)
userRV.layoutManager = LinearLayoutManager(this)
userRV.adapter = userRVAdapter
// fetching the first page of data
getDataFromAPI(page)
// adding scroll listener to recycler view for pagination
userRV.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// checking if we have reached the bottom and not already loading
if (!recyclerView.canScrollVertically(1) && !isLoading && page <= limit) {
// showing progress bar, incrementing page, and fetching next page
loadingPB.visibility = View.VISIBLE
page++
getDataFromAPI(page)
}
}
})
}
private fun getDataFromAPI(page: Int) {
// if requested page exceeds limit, show message and return
if (page > limit) {
Toast.makeText(this, "That's all the data..", Toast.LENGTH_SHORT).show()
loadingPB.visibility = View.GONE
return
}
// setting loading flag to true
isLoading = true
// constructing API URL
val url = "https://reqres.in/api/users?page=$page"
// creating request queue
val queue = Volley.newRequestQueue(this)
// creating JSON request
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET, url, null,
{ response ->
try {
// extracting data array from JSON response
val dataArray = response.getJSONArray("data")
// looping through array and adding to model list
for (i in 0 until dataArray.length()) {
val jsonObject = dataArray.getJSONObject(i)
userModalArrayList.add(
UserModal(
jsonObject.getString("first_name"),
jsonObject.getString("last_name"),
jsonObject.getString("email"),
jsonObject.getString("avatar")
)
)
}
// notifying adapter about new data
userRVAdapter.notifyDataSetChanged()
// hiding progress bar and resetting loading state
loadingPB.visibility = View.GONE
isLoading = false
} catch (e: JSONException) {
e.printStackTrace()
loadingPB.visibility = View.GONE
isLoading = false
}
}, {
// handling error
Toast.makeText(this@MainActivity, "Fail to get data..", Toast.LENGTH_SHORT).show()
loadingPB.visibility = View.GONE
isLoading = false
})
// adding request to queue
queue.add(jsonObjectRequest)
}
}
Note : To access the full android application check this repository: Pagination in Android RecyclerView using Volley
Output: