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: