Open In App

How to Implement Pagination in Android RecyclerView using Volley?

Last Updated : 21 Apr, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

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. 

What is Pagination?

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. 

What is the benefit of using Pagination in your lists of data? 

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:




Next Article

Similar Reads