How to add pagination in RecyclerView Android using Kotlin Android 02.07.2020

In this tutorial, we’ll be discussing and implementing Endless Scrolling or Infinite Scroll or Load More or Pagination on RecyclerView in Android Application.

The code for the activity_main.xml layout is given below:

<?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=".TestKotlinActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvItems"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:listitem="@layout/list_item" />

    <include
        android:id="@+id/llLoader"
        layout="@layout/loader"
        android:layout_height="130dp"
        android:layout_width="130dp"
        android:visibility="gone"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

The layout for the rows of the RecyclerView is defined in item_row.xml file as shown below:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/TextAppearance.AppCompat.Large"/>

    <TextView
        android:id="@+id/tvRating"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/TextAppearance.AppCompat.Small"/>
</LinearLayout>

The code for the ListAdapter and Movie classes is given below:

data class Movie(var title: String, var rating: Float)

class MovieAdapter(private val listItemClickListener: ListItemClickListener)
    : ListAdapter<Movie, RecyclerView.ViewHolder>(ListItemCallback()) {

    interface ListItemClickListener {
        fun onItemClick(item: Movie, position: Int)
    }

    class ListItemCallback : DiffUtil.ItemCallback<Movie>() {
        override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return oldItem.title == newItem.title && oldItem.rating == newItem.rating
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position)
        (holder as ListenItemViewHolder).bind(item, position)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        return ListenItemViewHolder(view)
    }

    inner class ListenItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tvTitle: TextView = itemView.tvTitle
        var tvRating: TextView = itemView.tvRating

        fun bind(item: Movie, position : Int) {
            tvTitle.text = item.title
            tvRating.text = "Rating: ${item.rating}"

            itemView.setOnClickListener {
                listItemClickListener.onItemClick(item, adapterPosition)
            }
        }
    }
}

Let’s look at the MainActivity class where we instantiate the above Adapter.

class MainActivity : AppCompatActivity(), MovieAdapter.ListItemClickListener {
    val TAG = Constants.TAG;
    private var movies = mutableListOf<Movie>()
    private lateinit var movieAdapter: MovieAdapter
    private var isLoading: Boolean = false
    private lateinit var layoutManager : LinearLayoutManager
    private var handler: Handler = Handler()

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

        setupRecyclerView()
    }

    private fun setupRecyclerView() {
        movieAdapter = MovieAdapter(this)
        layoutManager = LinearLayoutManager(this)
        rvItems.layoutManager = layoutManager
        rvItems.itemAnimator = DefaultItemAnimator()
        rvItems.adapter = movieAdapter
        loadData(isInitLoad = true)

        rvItems.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (!isLoading) {
                    if (layoutManager.findLastCompletelyVisibleItemPosition() == movies.size - 1) {
                        loadData()
                        isLoading = true
                    }
                }
            }
        })

        movieAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
                layoutManager.scrollToPositionWithOffset(positionStart, 0)
            }
        })
    }

    fun loadData(isInitLoad: Boolean = false) {
        llLoader.visibility = View.VISIBLE
        if (isInitLoad) {
            (0..19).mapTo(movies) { Movie("Movie $it", it.toFloat()) }
            movieAdapter.submitList(movies)
            llLoader.visibility = View.GONE
        } else {
            handler.postDelayed(Runnable {
                val start = movies.size
                val end = start + 10
                val newMovies = ArrayList<Movie>()
                (start..end).mapTo(newMovies) { Movie("Movie $it", it.toFloat()) }
                updateDataList(newMovies)
                isLoading = false
                llLoader.visibility = View.GONE
            },2000)
        }
    }

    fun updateDataList(newList:List<Movie>){
        val tempList = movies.toMutableList()
        tempList.addAll(newList)
        movieAdapter.submitList(tempList)
        movies = tempList
    }

    override fun onItemClick(item: Movie, position: Int) {
        Log.d(TAG, "item: $item");
    }
}