Endless list scrolling with RecyclerView or ListView in Kotlin in 4 steps with the easiest way using RestAPI

Abhishek Srivastava
5 min readNov 5, 2020

--

Hi Readers,

Today’s, Every mostly applications have Endless list scrolling features there i.e INSTAGRAM, FACEBOOK, FLIPKART, and so on…

So that this common feature in the application is to load automatically more items as the user scrolls through the items endless list.

There are so many prons like

1: You don’t want to make a huge API request call to the server, making your user wait infinitely or much time.

2: You don’t want to make your UI or App look like the 90's generation with Next and previous button.

In such a scenario implementing an endless scroll might be the ideal choice.

This choice is working like to provide the appearance of endless scrolling, it’s important to fetch data before the user gets to the end of the list. That the time you can add more value to call the next RESTAPI request to the server and get data and therefore need to append more data and show the user without block user UI and get the best user experience.0

If We have to implement an Endless list in our code that a big task and more difficult, especially for beginners. So here the solution with a simple and easy way to implement Endless list scrolling in our code within few steps. I have also pushed the code in GITHUB and you can find the complete code at my GitHub link which is mentioned below.

So Readers, Can We Start?

Yes !! We are ready.

Sample of API URL:

{{baseURL}}/api/{{endpoint}}??page={{currentPageNumber}}

Sample of API response:

{
page: 1, //This is current page
pages: 108, //This is total pages

otherObjectKey: [otherResponseObject]
}

Page and Pages is mandatory in API response to use Endless list Scrolling

STEP 1:

First, You have implemented Retrofit APICLIENT and API SERVICE to establish a connection and get data from the server.

Here, We need the below classes and dependencies

i)Dependencies

ii) ApiClient

iii)ApiService

Add Dependencies in your build.gradle

//TODO Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//TODO GLIDE implementation for fetch images
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'androidx.cardview:cardview:1.0.0'
//TODO For dimension
implementation 'com.intuit.sdp:sdp-android:1.0.6'

ApiClient.kt

package com.github.abhi10jul.endlesslistscrolling_kotlinexample

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

/**
* @author abhishek srivastava.
*/
class ApiClient {
private val BASE_URL = "{{ADD YOUR BASE URL}}"
private var apiService: ApiService = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)

fun callTvShowListRequest(page: Int, callback: Callback<ShowModel>) {
apiService.getTvShowList(page).enqueue(callback)
}
}

ApiService.kt

package com.github.abhi10jul.endlesslistscrolling_kotlinexample

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

/**
* @author abhishek srivastava.
*/
interface ApiService {

@GET("{{ADD YOUR END POINT}}")
fun getTvShowList(@Query("page") page: Int): Call<ShowModel>

}

STEP 2:

Add Your model class base on response and make sure ‘PAGE’ & ‘PAGES’ is there in response.

i) ShowModel.kt

ii) TvShow.kt

ShowModel.kt


package com.github.abhi10jul.endlesslistscrolling_kotlinexample

import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

/**
* @author abhishek srivastava.
*/
data class ShowModel(
@SerializedName("total")
@Expose val total: String,
@SerializedName("page")
@Expose
val page: Int,
@SerializedName("pages")
@Expose
val pages: Int,
@SerializedName("tv_shows")
@Expose
val tvShows: ArrayList<TvShow>
)

TVShow.kt

data class TvShow(
@SerializedName("id")
@Expose
val id: Int,
@SerializedName("name")
@Expose
val name: String,
@SerializedName("status")
@Expose
val status: String,
@SerializedName("image_thumbnail_path")
@Expose val imageThumbnailPath: String
)

STEP 3:

Now We can design the UI part for “main view” and “child view”.

i)activity_main.xml

ii)show_list_raw.xml

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<
layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">

<
RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<
TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="@dimen/_40sdp"
android:layout_alignParentTop="true"
android:background="@color/purple_200"
android:gravity="center_vertical|center_horizontal"
android:text="Endless Scrolling RecyclerView"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="@color/white"
android:textStyle="bold" />

<
ProgressBar
android:id="@+id/defaultProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvTitle"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:visibility="gone" />

<
androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/loadMoreProgress"
android:layout_below="@+id/defaultProgress" />

<
ProgressBar
android:id="@+id/loadMoreProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:visibility="gone" />
</
RelativeLayout>
</
layout>

show_list_raw.xml

<?xml version="1.0" encoding="utf-8"?>
<
layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<
androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_5sdp"
android:layout_marginTop="@dimen/_2sdp"
android:layout_marginEnd="@dimen/_5sdp"
android:layout_marginBottom="@dimen/_2sdp"
app:cardCornerRadius="@dimen/_20sdp">

<
LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:orientation="horizontal">

<
ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="@dimen/_70sdp"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="1.7"
android:contentDescription="@string/app_name"
android:scaleType="centerCrop" />


<
TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="1"
android:background="@null"
android:fontFamily="sans-serif"
android:gravity="center_horizontal|center_vertical"
android:padding="@dimen/_2sdp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/black" />
</
LinearLayout>
</
androidx.cardview.widget.CardView>
</layout>

STEP 4:

Now We can set up the adapter to bind data in RecylerView. Here I have created a custom way to the adapter for updating list because sometimes in KOTLIN notify method i.e ‘notifyDataSetChanged(), notifyItemRangeInserted() and so on…’ is not worked. So I can use a custom method to update the list.

So I have created fun updateList(tvShowList: ArrayList<TvShow>, oldCount: Int, tvShowListSize: Int)” in MainActivityAdapter class.

And also implement logic in “MainActivity.kt” to handle all “Endless list scrolling ”.

i)MainActivityAdapter.kt

ii)MainActivity.kt

MainActivityAdapter.kt

package com.github.abhi10jul.endlesslistscrolling_kotlinexample

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.github.abhi10jul.endlesslistscrolling_kotlinexample.databinding.ShowListRawBinding

/**
* @author abhishek srivastava.
*/
class MainActivityAdapter() :
RecyclerView.Adapter<MainActivityAdapter.MainActivityAdapterHolder>() {
private var tvShowList = ArrayList<TvShow>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainActivityAdapterHolder {
return MainActivityAdapterHolder(
LayoutInflater.from(parent.context).inflate(R.layout.show_list_raw, parent, false)
)
}

override fun onBindViewHolder(holder: MainActivityAdapterHolder, position: Int) {
holder.binding.tvTitle.text = "${tvShowList[position].name}"
Glide
.with(holder.itemView)
.load(tvShowList[position].imageThumbnailPath)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(holder.binding.thumbnail)
}

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

class MainActivityAdapterHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = ShowListRawBinding.bind(itemView)
}

fun updateList(tvShowList: ArrayList<TvShow>, oldCount: Int, tvShowListSize: Int) {
this.tvShowList = tvShowList
notifyDataSetChanged()
notifyItemRangeInserted(oldCount, tvShowListSize)
}
}

MainActivity.kt

package com.github.abhi10jul.endlesslistscrolling_kotlinexample

import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.abhi10jul.endlesslistscrolling_kotlinexample.databinding.ActivityMainBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {
private val TAG = MainActivity::class.java.simpleName
private lateinit var binding: ActivityMainBinding
private lateinit var mainActivityAdapter: MainActivityAdapter
private var apiClient = ApiClient()
private var currentPage = 1
private var totalAvailablePages = 1
private lateinit var tvShowList: ArrayList<TvShow>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
initViews()
}


private fun initViews() {
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
tvShowList = ArrayList()
mainActivityAdapter = MainActivityAdapter()
binding.recyclerview.adapter = mainActivityAdapter
binding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!binding.recyclerview.canScrollVertically(1)) {
if (currentPage <= totalAvailablePages) {
currentPage += 1
loadPageList()
}
}
}
})
loadPageList()
}

private fun loadPageList() {
toogleLoading()
apiClient.callTvShowListRequest(currentPage, object : Callback<ShowModel> {
override fun onResponse(call: Call<ShowModel>, response: Response<ShowModel>) {
if (response.isSuccessful) {
val showModel = response.body()
if (showModel != null) {
val oldCount = tvShowList.size
totalAvailablePages = showModel.pages
tvShowList.addAll(showModel.tvShows)
mainActivityAdapter.updateList(tvShowList, oldCount, tvShowList.size)
Log.e(
TAG,
"oldCount $oldCount totalAvailablePages $totalAvailablePages tvShowList ${tvShowList.size}"
)
}
}
toogleLoading()
}

override fun onFailure(call: Call<ShowModel>, t: Throwable) {
t.printStackTrace()
Log.e(TAG, "exception", t)
toogleLoading()
}
})
}

private fun toogleLoading() {
if (currentPage == 1) {
if (binding.defaultProgress.isShown) {
binding.defaultProgress.visibility = View.GONE
}
else {
binding.defaultProgress.visibility = View.VISIBLE
}
}
else {
if (binding.loadMoreProgress.isShown) {
binding.loadMoreProgress.visibility = View.GONE
}
else {
binding.loadMoreProgress.visibility = View.VISIBLE
}
}
}
}

Hope you learn

Thanks for reading!

Special Thanks to ALL ❤️

--

--

Abhishek Srivastava
Abhishek Srivastava

Written by Abhishek Srivastava

I am Software Developer having Java, Java Spring boot Rest services, Android, Kotlin, Flutter skills and Android SDK libraries...

Responses (1)