This post contains affiliate links. If you use these links to buy something I may earn a commission. Thanks.”

We are using lots of apps that load data while we scrolling. Facebook, Instagram, and more apps using this technique. This technique helps users to wait less time.

So if you are planning to make an app like that, using RecyclerView. Then this post is for you.

In this post, You will learn how to use Load More on Scroll or pagination or endless scrolling or infinite scroll or whatever you call, with RecyclerView.

Before that, I recommend reading our RecyclerView Tutorial Guide.

This post is not dealing with the pagination library.

How To Implement Pagination In Android RecyclerView

In this post, I am going to talk about two methods, that implement pagination in RecyclerView. But in both methods, we will use scroll Listener with RecyclerView to listen to scroll events. When RecyclerView scrolls, onScrolled() method gets called.

So first learn how to add OnScrollListener with Recyclerview.

How To Add OnScrollListener With RecyclerView


    //attaches scrollListener with RecyclerView
        recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener()
        {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int)
            {
                super.onScrolled(recyclerView, dx, dy)

              

            }
            })


Simple Android RecyclerView Load More On Scroll Example

Let’s make a linear list Using RecyclerView and add an endless scroll feature.

pagination in android recyclerview

After the implementation of scroll listener, we should do the following

  • we use findLastCompletelyVisibleItemPosition() to get the position of last visible item.
  • Use a variable which checks progressbar loading or not
  • If last item reached and progressbar not loading, loadmore() gets called. Using Handler, we add “load” in list.
  • When adapter checks list, ProgressBar gets added.
  • Next data chunks received, ProgressBar gets removed and data got added.
  • For showing different View types. here
    1. Data row
    2. ProgressBar

    Make use of getViewType() method in RecyclerView.Adapter.

MainActivity.kt

class MainActivity : AppCompatActivity()
{
    //handler instance
    var handler: Handler = Handler()
    //list for holding data
    lateinit var list : ArrayList

    lateinit var adapter : RecyclerViewAdapter
    //Variable for checking progressbar loading or not
    private var isLoading: Boolean = false
    lateinit var layoutManager : LinearLayoutManager



    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        layoutManager = LinearLayoutManager(this)
        //attaches LinearLayoutManager with RecyclerView
        recyclerview.layoutManager = layoutManager

        list = ArrayList()

        load()

        adapter = RecyclerViewAdapter(list)

        recyclerview.adapter = adapter

        addScrollerListener()

    }

    private fun addScrollerListener()
    {
        //attaches scrollListener with RecyclerView
        recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener()
        {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int)
            {
                super.onScrolled(recyclerView, dx, dy)

                if (!isLoading)
                {
                    //findLastCompletelyVisibleItemPostition() returns position of last fully visible view.
                    ////It checks, fully visible view is the last one.
                    if (layoutManager.findLastCompletelyVisibleItemPosition() == list.size - 1)
                    {

                        loadMore()
                        isLoading = true

                    }
                }

            }
            })
    }

    private fun loadMore()
    {
        //notify adapter using Handler.post() or RecyclerView.post()
        handler.post(Runnable
        {
            list.add("load")
            adapter.notifyItemInserted(list.size - 1)

        })

        handler.postDelayed(Runnable {

            //removes "load".
            list.removeAt(list.size - 1)
            var listSize = list.size

            adapter.notifyItemRemoved(listSize)
            //sets next limit
            var nextLimit = listSize + 10

            for(i in listSize until nextLimit)
            {
                list.add("Item No $i")
            }

            adapter.notifyDataSetChanged()
            isLoading = false

        },2500)
    }


    private fun load()
    {
        for(i in 0..9)
        {
            list.add("Item No: $i")
        }
    }

}

 

  • It's better to use Handler.post() or RecyclerView.post() while updating adapter when RecyclerView measures.
  • findLastCompletelyVisibleItemPosition() returns only completely visible Item's position, So make sure your last view is fully visible.
  • 2500 milliseconds - delay time

RecyclerViewAdapter.kt

package m.example.simplerecyclerviewloadmoreex_kotlin

import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row.view.*

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import java.lang.IllegalArgumentException

class RecyclerViewAdapter(var list: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
    companion object
    {
        private const val VIEW_TYPE_DATA = 0;
        private const val VIEW_TYPE_PROGRESS = 1;
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewtype: Int): RecyclerView.ViewHolder
    {
        return when (viewtype)
        {
            VIEW_TYPE_DATA ->
            {//inflates row layout
                val view = LayoutInflater.from(parent.context).inflate(R.layout.row,parent,false)
                DataViewHolder(view)
            }

            VIEW_TYPE_PROGRESS ->
            {//inflates progressbar layout
                val view = LayoutInflater.from(parent.context).inflate(R.layout.progressbar,parent,false)
                ProgressViewHolder(view)
            }
            else -> throw IllegalArgumentException("Different View type")

        }
    }

    override fun getItemCount(): Int
    {
        return list.size
    }

    override fun getItemViewType(position: Int): Int
    {
        var viewtype = list.get(position)

        return when(viewtype)
        {//if data is load, returns PROGRESSBAR viewtype.
            "load"  -> VIEW_TYPE_PROGRESS
            else -> VIEW_TYPE_DATA
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, p1: Int)
    {

        if (holder is DataViewHolder)
        {
            holder.textview.text = list.get(p1)
        }
    }

    inner class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    {
        var textview = itemView.textview

        init {
            itemView.setOnClickListener {
                Toast.makeText(itemView.context,list.get(adapterPosition),Toast.LENGTH_LONG).show()
            }
        }
    }

    inner class ProgressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

}

 

  • getItemViewType(int position) method returns VIEW_TYPE_PROGRESS, if data is "load". Otherwise, it returns VIEW_TYPE_DATA.
  • Based on ViewType, In onCreateViewHolder layout will get inflated.

Grid RecyclerView Pagination Example - Different Approach

Now we will make a Grid RecyclerView with pagination. Sometimes we need to load data before the last item loads.

grid android recyclerview infinite scroll example pagination

For that purpose, you can make use of this approach.

Okay, what we do in this approach.

we define 3 variables, visibleThreshold, lastVisibleItem, and totalItemCount.

  • visibleThreshold : limit of items that user want to see before loading next items
  • lastVisibleItem : just like name, last visible item position in adapter
  • totalItemCount : No.of items in adapter

after the scroll listener setup,

  • When onScrolled() gets called, just checks totalItemCount <= lastVisibleItem + visibleThreshold with isLoading : variable for storing progressbar state.
  • if both are true, next chunks of data get loaded
class MainActivity : AppCompatActivity()
{
    lateinit var list : ArrayList
    lateinit var adapter : RecyclerViewAdapter
    private var isLoading: Boolean = false
    lateinit var gridlayoutManager : GridLayoutManager


    private var visibleThreshold = 5
    private var lastVisibleItem = 0
    private var totalItemCount = 0

    //handler instance
    private var handler: Handler = Handler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //no of columns or span in the grid.
        var columns = 2

        gridlayoutManager = GridLayoutManager(this, columns)
        recyclerview.layoutManager = gridlayoutManager

        load()

        adapter = RecyclerViewAdapter(list)

        recyclerview.adapter = adapter

        addScrollerListener()

        gridlayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup()
        {
            //Each item occupies 1 span by default.
            override fun getSpanSize(p0: Int): Int
            {
                return when (adapter.getItemViewType(p0))
                {
                    //returns total no of spans, to occupy full sapce for progressbar
                    RecyclerViewAdapter.VIEW_TYPE_PROGRESS -> gridlayoutManager.spanCount
                    RecyclerViewAdapter.VIEW_TYPE_DATA -> 1
                    else -> -1
                }
            }
        }
    }

      private  fun addScrollerListener()
        {
            recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener()
            {
                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int)
                {
                    super.onScrolled(recyclerView, dx, dy)

                    //total no. of items
                    totalItemCount = gridlayoutManager.itemCount
                    //last visible item position
                    lastVisibleItem = gridlayoutManager.findLastCompletelyVisibleItemPosition()

                    if(!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold))
                    {
                        loadMore()
                        isLoading = true
                    }
                }
            })
        }



    private fun loadMore()
    {
        handler.post(Runnable
        {
            //adds load item in list
            list.add("load")
            adapter.notifyItemInserted(list.size - 1)

        })

        recyclerview.postDelayed(Runnable {

            //removes load item in list.
            list.removeAt(list.size - 1)
            var listSize = list.size

            adapter.notifyItemRemoved(listSize)
            //next limit
            var nextLimit = listSize + 10

            for(i in listSize until nextLimit)
            {
                list.add("Item No $i")
            }

            adapter.notifyDataSetChanged()
            isLoading = false

        },7000)

    }

    private fun load()
    {
        list = ArrayList()

        for(i in 0..9)
        {
            list.add("Item $i")
        }
    }

}

  • Make ProgressBar width to maximum using setSpanSizeLookup() method

 

RecyclerViewAdapter.kt

 

class RecyclerViewAdapter(var list: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
    companion object
    {
        const val VIEW_TYPE_DATA = 0;
        const val VIEW_TYPE_PROGRESS = 1;
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewtype: Int): RecyclerView.ViewHolder
    {
        return when (viewtype)
        {
            VIEW_TYPE_DATA ->
            {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.row,parent,false)
                DataViewHolder(view)
            }

            VIEW_TYPE_PROGRESS ->
            {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.progressbar,parent,false)
                ProgressViewHolder(view)
            }
            else -> throw IllegalArgumentException("Different View type")

        }
    }

    override fun getItemCount(): Int
    {
        return list.size
    }

    override fun getItemViewType(position: Int): Int
    {
        var viewtype = list.get(position)
        return when(viewtype)
        {
            "load"  -> VIEW_TYPE_PROGRESS
            else -> VIEW_TYPE_DATA
        }
    }

    override fun onBindViewHolder(p0: RecyclerView.ViewHolder, p1: Int)
    {

        if (p0 is DataViewHolder)
        {
            p0.textview.text = list.get(p1)
        }
    }

    inner class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    {
        var textview = itemView.textview

        init {
            itemView.setOnClickListener {
                Toast.makeText(itemView.context,list.get(adapterPosition),Toast.LENGTH_LONG).show()
            }
        }
    }

    inner class ProgressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
    
}

Retrofit Android RecyclerView Infinite Scroll Example

In this example, we will load data from the internet using Retrofit2. You can download PHP(data.php) code from the Source Code link shown at the top.

I am using Android Studio 3.5.


Let's create a project.

In choosing your project window, click on "Empty Activity" and click Next.

Name: RetrofitRecyclerViewLoadMoreEx

Package name: androidride.com

Select language Kotlin

I am going to use AndroidX library, so make sure you tick on the androidx.* artifacts.
That's your choice, but I recommend to use this one.

Click "Finish".


add below dependencies

implementation 'com.squareup.retrofit2:retrofit:2.0.2'
implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
  • Gson converter : It helps to serialize and deserialize JSON data.

build.gradle(Module:App)

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.androidride.retrofitrecyclerviewloadmoreex"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'


    implementation 'com.squareup.retrofit2:retrofit:2.0.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'

}

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>

 

strings.xml

<resources>
    <string name="app_name">Retrofit RecyclerView LoadMore Ex - Kotlin</string>
</resources>

styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

 

  • Don't forget to add INTERNET permission in AndroidManifest.xml like below

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.androidride.retrofitrecyclerviewloadmoreex">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Data.kt

package com.androidride.retrofitrecyclerviewloadmoreex

data class Data(var category: String)
{
    var title: String? = null
    var subtitle: String? = null

    init {
        this.category = category
    }
}

DataApi.kt

package com.androidride.retrofitrecyclerviewloadmoreex

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface DataApi
{
@GET("data.php")
fun getData(@Query("index") index : Int): Call<List<Data>>
}
  • In Retrofit2, endpoints defined in Interface with special annotations.
  • This endpoint returns List.

MainActivity.kt

package com.androidride.retrofitrecyclerviewloadmoreex

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit


class MainActivity : AppCompatActivity()
{
    lateinit var list: ArrayList
    lateinit var adapter: RecyclerViewAdapter

    var notLoading = true
    lateinit var layoutManager: LinearLayoutManager
    lateinit var api: DataApi
    lateinit var retrofit: Retrofit


    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        list = ArrayList()
        adapter = RecyclerViewAdapter(list)
        recyclerview.setHasFixedSize(true)

        layoutManager = LinearLayoutManager(this)
        recyclerview.layoutManager = layoutManager
        recyclerview.adapter = adapter

        retrofit = RetrofitInstance.getRetrofitInstance()
        api = retrofit.create(DataApi::class.java)

        load(0)

        addscrolllistener()
    }

    private fun addscrolllistener()
    {
        recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener()
        {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int)
            {
                if(notLoading &&  layoutManager.findLastCompletelyVisibleItemPosition() == list.size -1 )
                {
                    list.add(Data("progress"))
                    adapter.notifyItemInserted(list.size - 1)
                    notLoading = false

                    val call: Call<List> = api.getData(list.size - 1)

                    call.enqueue( object :  Callback<List>
                    {
                        override fun onResponse(call: Call<List>?, response: Response<List>?)
                        {
                        list.removeAt(list.size - 1)
                        adapter.notifyItemRemoved(list.size)

                                if(response!!.body().isNotEmpty())
                                {
                                    list.addAll(response.body())
                                    adapter.notifyDataSetChanged()
                                    notLoading = true
                                }
                                else
                                {
                                    Toast.makeText(applicationContext,"End of data reached", Toast.LENGTH_LONG).show()
                                }

                        }

                        override fun onFailure(call: Call<List>?, t: Throwable?) {
                            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
                        }
                    })
                }
            }
        })
    }

    private fun load(i: Int)
    {
        val call: Call<List> = api.getData(0)

        call.enqueue(object : Callback<List>
        {
            override fun onResponse(call: Call<List>?, response: Response<List>?)
            {
                if(response!!.isSuccessful)
                {
                    list.addAll(response!!.body())
                    adapter.notifyDataSetChanged()
                }

            }

            override fun onFailure(call: Call<List>?, t: Throwable?)
            {

            }
        })

    }
}


RetrofitInstance.kt

package com.androidride.retrofitrecyclerviewloadmoreex

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class RetrofitInstance
{
    companion object
    {
        fun getRetrofitInstance(): Retrofit
        {
            val retrofit = Retrofit.Builder().baseUrl("https://www.androidride.com/").addConverterFactory(
                GsonConverterFactory.create()).build()

            return retrofit

        }
    }
}

  • For performing network requests, we need to create Retrofit Instance using Retrofit.Builder() using Base Url

RecyclerViewAdapter.kt

package com.androidride.retrofitrecyclerviewloadmoreex;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import org.w3c.dom.Text;

import java.util.ArrayList;

public class RecyclerViewAdapter extends RecyclerView.Adapter
{

    public static final int TYPE_DATA = 0;
    public static final int TYPE_PROGRESS = 1;

    ArrayList list;

        public RecyclerViewAdapter(Context context,ArrayList list)
        {
            this.list = list;
        }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
    {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

            if(viewType == TYPE_DATA)
            {
                return new DataHolder(inflater.inflate(R.layout.row,parent,false));
            }
            else
            {
                return new ProgressHolder(inflater.inflate(R.layout.progressbar,parent,false));
            }

    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
    {
            if(holder instanceof  DataHolder)
            {
                ((DataHolder) holder).textTitle.setText(list.get(position).title);
                ((DataHolder) holder).textSubtitle.setText(list.get(position).subtitle);
            }
    }

    @Override
    public int getItemCount()
    {
        return list ==  null ? 0 : list.size() ;
    }

    @Override
    public int getItemViewType(int position)
    {
        if(list.get(position).category.equals("data"))
        {
            return TYPE_DATA;
        }
        else
         {
            return TYPE_PROGRESS;
         }
    }

    class DataHolder extends RecyclerView.ViewHolder
    {
        TextView textTitle,textSubtitle;

        public DataHolder(@NonNull final View itemView)
        {
            super(itemView);

            textTitle =(TextView)itemView.findViewById(R.id.title);
            textSubtitle = (TextView)itemView.findViewById(R.id.subtitle);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view)
                {
                    Toast.makeText(itemView.getContext(),"Clicked Item: "+list.get(getAdapterPosition()).subtitle,Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    class ProgressHolder extends RecyclerView.ViewHolder
    {
        public ProgressHolder(@NonNull View itemView)
        {
            super(itemView);
        }
    }
}


retrofit android recyclerview pagination example

Thanks for scrolling. If you have liked this post, please share it with your friends and family.

Please disable your adblocker or whitelist this site!