How to add Load More / Infinite Scrolling in Android using Kotlin

In this tutorial, I’m going to show you how to add the Load More / Infinite Scrolling feature in the RecyclerView that has a Linear, Grid or Staggered Grid Layout.

But first, let’s set up the files which are important for whatever layout you choose to implement.

Setting up Load More / Infinite Scrolling

First, let’s start by creating the layout that contains the circular progress bar we show when we load more data (progress_loading.xml):

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal"> <ProgressBar android:id="@+id/progressbar" android:layout_width="24dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout> </LinearLayout>
Code language: HTML, XML (xml)

After that, create the ScrollListener for our RecyclerView (RecyclerViewLoadMoreScroll.kt):

class RecyclerViewLoadMoreScroll : RecyclerView.OnScrollListener { private var visibleThreshold = 5 private lateinit var mOnLoadMoreListener: OnLoadMoreListener private var isLoading: Boolean = false private var lastVisibleItem: Int = 0 private var totalItemCount:Int = 0 private var mLayoutManager: RecyclerView.LayoutManager fun setLoaded() { isLoading = false } fun getLoaded(): Boolean { return isLoading } fun setOnLoadMoreListener(mOnLoadMoreListener: OnLoadMoreListener) { this.mOnLoadMoreListener = mOnLoadMoreListener } constructor(layoutManager:LinearLayoutManager) { this.mLayoutManager = layoutManager } constructor(layoutManager:GridLayoutManager) { this.mLayoutManager = layoutManager visibleThreshold *= layoutManager.spanCount } constructor(layoutManager:StaggeredGridLayoutManager) { this.mLayoutManager = layoutManager visibleThreshold *= layoutManager.spanCount } override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (dy <= 0) return totalItemCount = mLayoutManager.itemCount if (mLayoutManager is StaggeredGridLayoutManager) { val lastVisibleItemPositions = (mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null) // get maximum element within the list lastVisibleItem = getLastVisibleItem(lastVisibleItemPositions) } else if (mLayoutManager is GridLayoutManager) { lastVisibleItem = (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition() } else if (mLayoutManager is LinearLayoutManager) { lastVisibleItem = (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition() } if (!isLoading && totalItemCount <= lastVisibleItem + visibleThreshold) { mOnLoadMoreListener.onLoadMore() isLoading = true } } private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int { var maxSize = 0 for (i in lastVisibleItemPositions.indices) { if (i == 0) { maxSize = lastVisibleItemPositions[i] } else if (lastVisibleItemPositions[i] > maxSize) { maxSize = lastVisibleItemPositions[i] } } return maxSize } }
Code language: Kotlin (kotlin)

In this example, we set the visibleThreshold to 5, which means the circular progress bar will show up when the user sees the 5th item from the end of our downloaded data.

Next, we have to create an interface where we are calling to load more data into our RecyclerView (OnLoadMoreListener):

interface OnLoadMoreListener { fun onLoadMore() }
Code language: Kotlin (kotlin)

Also, we need to create a file (if you don’t have one already in your project) with a name Constant.kt and paste the following code:

object Constant { const val VIEW_TYPE_ITEM = 0 const val VIEW_TYPE_LOADING = 1 }
Code language: Kotlin (kotlin)

This file contains the IDs for our Item View and Loading View, so the RecyclerView can call the right view in the right moment.

Now we go to our file RecyclerViewActivity.kt where the RecyclerView lives, and we’re going to create an ArrayList called loadMoreItemsCells.

loadMoreItemsCells contains the items we load when we show the circular progress bar (Loading View).

Also, we call the methods setRVLayoutManager() and we set our RecyclerView Layout and setRVScrollListener(), where we add the ScrollListener we created before:

class RecyclerViewActivity : AppCompatActivity() { lateinit var loadMoreItemsCells: ArrayList<String?> lateinit var adapter: Items_RVAdapter lateinit var scrollListener: RecyclerViewLoadMoreScroll lateinit var mLayoutManager:RecyclerView.LayoutManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_recycler_view) //** Get the data for our ArrayList //** Set the adapter of the RecyclerView adapter = Items_RVAdapter(itemsCells) adapter.notifyDataSetChanged() items_rv.adapter = adapter //** Set the Layout Manager of the RecyclerView setRVLayoutManager() //** Set the scrollListerner of the RecyclerView setRVScrollListener() } private fun LoadMoreData() { //Add the Loading View adapter.addLoadingView() //Create the loadMoreItemsCells Arraylist loadMoreItemsCells = ArrayList() //Get the number of the current Items of the main Arraylist val start = adapterLinear.itemCount //Load 16 more items val end = start + 16 //Use Handler if the items are loading too fast. //If you remove it, the data will load so fast that you can't even see the LoadingView Handler().postDelayed({ for (i in start..end) { //Get data and add them to loadMoreItemsCells ArrayList loadMoreItemsCells.add("Item $i") } //Remove the Loading View adapterLinear.removeLoadingView() //We adding the data to our main ArrayList adapterLinear.addData(loadMoreItemsCells) //Change the boolean isLoading to false scrollListener.setLoaded() //Update the recyclerView in the main thread items_linear_rv.post { adapterLinear.notifyDataSetChanged() } }, 3000) } }
Code language: Kotlin (kotlin)

But we’re not finished yet!

Now in the Recycler’s View Adapter file we’re going to add some methods that add the data we downloaded during Loading (loadMoreItemsCells) to our main ArrayList (itemsCells), and also add methods that know when to show or hide our Loading View:

class Items_RVAdapter(private var itemsCells: ArrayList<String?>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { lateinit var mcontext: Context class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) class LoadingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) fun addData(dataViews: ArrayList<String?>) { this.itemsCells.addAll(dataViews) notifyDataSetChanged() } fun getItemAtPosition(position: Int): String? { return itemsCells[position] } fun addLoadingView() { //Add loading item Handler().post { itemsCells.add(null) notifyItemInserted(itemsCells.size - 1) } } fun removeLoadingView() { //Remove loading item if (itemsCells.size != 0) { itemsCells.removeAt(itemsCells.size - 1) notifyItemRemoved(itemsCells.size) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { mcontext = parent.context return if (viewType == Constant.VIEW_TYPE_ITEM) { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_row, parent, false) ItemViewHolder(view) } else { val view = LayoutInflater.from(mcontext).inflate(R.layout.progress_loading, parent, false) LoadingViewHolder(view) } } override fun getItemCount(): Int { return itemsCells.size } override fun getItemViewType(position: Int): Int { return if (itemsCells[position] == null) { Constant.VIEW_TYPE_LOADING } else { Constant.VIEW_TYPE_ITEM } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder.itemViewType == Constant.VIEW_TYPE_ITEM) { holder.itemView.itemtextview.text = itemsCells[position] } } }
Code language: Kotlin (kotlin)

Linear Layout

For a RecyclerView with Linear Layout we have to add the following methods in our RecyclerViewActivity.kt file:

private fun setRVLayoutManager() { mLayoutManager = LinearLayoutManager(this) items_rv.layoutManager = mLayoutManager items_rv.setHasFixedSize(true) } private fun setRVScrollListener() { mLayoutManager = LinearLayoutManager(this) scrollListener = RecyclerViewLoadMoreScroll(mLayoutManager as LinearLayoutManager) scrollListener.setOnLoadMoreListener(object : OnLoadMoreListener { override fun onLoadMore() { LoadMoreData() } }) items_rv.addOnScrollListener(scrollListener) }
Code language: Kotlin (kotlin)

That’s it! Run the project and…

Grid Layout

A RecyclerView with Grid Layout is a little bit different than the other layouts, and we have to add the following methods into our RecyclerViewActivity.kt file:

private fun setRVLayoutManager() { mLayoutManager = GridLayoutManager(this, 3) items_grid_rv.layoutManager = mLayoutManager items_grid_rv.setHasFixedSize(true) items_grid_rv.adapter = adapterGrid (mLayoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { return when (adapterGrid.getItemViewType(position)) { VIEW_TYPE_ITEM -> 1 VIEW_TYPE_LOADING -> 3 //number of columns of the grid else -> -1 } } } } private fun setRVScrollListener() { scrollListener = RecyclerViewLoadMoreScroll(mLayoutManager as GridLayoutManager) scrollListener.setOnLoadMoreListener(object : OnLoadMoreListener { override fun onLoadMore() { LoadMoreData() } }) items_grid_rv.addOnScrollListener(scrollListener) }
Code language: Kotlin (kotlin)

The difference in Grid Layout from the others is that we have to set the grid columns to 1 to be the full width of the recyclerView, when we want to show the Loading View.

Let’s run the project!

Staggered Grid Layout

The Layout Manager of a RecyclerView with a Staggered Layout is the same as Linear:

private fun setRVLayoutManager() { mLayoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) items_rv.layoutManager = mLayoutManager items_rv.setHasFixedSize(true) items_rv.adapter = adapterStaggered } private fun setRVScrollListener() { scrollListener = RecyclerViewLoadMoreScroll(mLayoutManager as StaggeredGridLayoutManager) scrollListener.setOnLoadMoreListener(object : OnLoadMoreListener { override fun onLoadMore() { LoadMoreData() } }) items_staggered_rv.addOnScrollListener(scrollListener) }
Code language: Kotlin (kotlin)

But it’s a little bit tricky when it comes to showing our Loading View in full width.

It doesn’t have the helper class .spanSizeLookup as the Grid Layout has. So we need to add the following code to our RecyclerView’s Adapter:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder.itemViewType == Constant.VIEW_TYPE_ITEM) { holder.itemView.itemtextview.text = itemsCells[position] }else{ val layoutParams = holder.itemView.layoutParams as StaggeredGridLayoutManager.LayoutParams layoutParams.isFullSpan = true } }
Code language: Kotlin (kotlin)

And run it!

You can find the final project here

If you have any questions feel free to DM me on Twitter @johncodeos or leave a comment below!

Subscribe
Notify of
guest
10 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ivan

Thanks so much for the lesson. However, I would like to know how you can use this method for LinearLayoutManager and an array of type ArrayList<Model> where it is not ArrayList<String>

osama altawil

When pulling up, the recycler view will re-load the data every time the pull is made up, in addition to a value

total_item_count=0

and
lastVisibleItem = -1 and visibleThreshold=5

lastVisibleItem + visibleThreshold=4

Osama AL Tawil

I know that, but I don’t know why it equals -1, I put it on logcat and it equals -1

Osama AL Tawil

yes,I printed last visible item, which is equal to -1, even though item count in adapter = 19 item

osama altawil
sagar

i am also facing same everytime i scroll it’s calling load more