Implement Paging 3 Library in Android Kotlin

Abhishek Srivastava
6 min readApr 23, 2023

Android team is continuously releasing new things to help us building Great Android Applications. Another addition to the list is Android Jetpack Paging 3.0 Library.

Paging 3 helps when working with RecyclerViews in our Android project to display a list with a lot of items in it. Sometimes we have use-case like fetching a huge list of songs list. Loading a lot of data at once in a list is not a very efficient task to do. To get over this, we have paging in Android.

What is Paging?

Paging a technique to use huge no of data to display as per user requirement of whenever needed which is efficient rather than fetching this many items at once and create view components for it. It will requires too much network bandwidth (if data is coming remotely) and it will also use too much system resources to display that huge data set at once.

What does paging 3.0 Library?

The paging library is the easiest way to display data in a listing format. Currently, Paging 3 Library is available with an alpha release, whereas if you want to work with a stable version of Paging Library then Paging 2 is available without Kotlin coroutine flow.

Paging Library Components

To implement paging functionality in our android app, we will use the following Paging library components:

This demographic workflow will be about on Paging 3 Library in Android

Benefits of using the Paging 3 library

  • Provides in-memory caching of the paged data that assures the systematic use of device resources.
  • Paging 3 library support coroutine flow
  • List separators (Decoration with Item )
  • Prevents duplication of the API request, ensuring that your app uses network bandwidth and system resources efficiently.
  • Configurable RecyclerView adapters automatically request data as the user scrolls toward the end of the loaded data.
  • Finest support for Kotlin coroutines and Flow, as well as LiveData and RxJava.
  • Built-in functionality to add loading state headers, footers, and list separators.
  • Built-in support for error handling, including refresh and retry capabilities.

Implement Paging 3 Library in Android Kotlin

The below process will guide you that how to implement Paging 3 in your
application.

Step 1: Implementation of Library into Project:

First add paging 3 library in app gradle app.gradle file.

 implementation "androidx.paging:paging-runtime-ktx:3.1.0-beta01"
//optional to handle Serializable
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

plugins {
id 'kotlin-android'
id 'kotlin-parcelize'
}

Step 2: Generating Data Models

Now, we have to create the data class for the api response to handle and display output.

import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize

@Parcelize
data class PassengerListResponse(
@SerializedName("totalPassengers") val totalPassengers: Int,
@SerializedName("totalPages") val totalPages: Int,
@SerializedName("data") val passengerList: List<Passenger>
) : Parcelable
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize

@Parcelize
data class Passenger(
@SerializedName("_id") val id: String,
@SerializedName("name") val name: String,
@SerializedName("trips") val trips: Int,
@SerializedName("__v") val visits: Int
) : Parcelable

Step 3: Integrate Source File

To get the data from backend API, we need a PagingSource.

class PassengerItemDataSource (private val dataRepository: DataRepository):PagingSource<Int, Passenger>() {
companion object {
private const val STARTING_PAGE_INDEX = 1
}


override fun getRefreshKey(state: PagingState<Int, Passenger>): Int? {
// We need to get the previous key (or next key if previous is null) of the page
// that was closest to the most recently accessed index.
// Anchor position is the most recently accessed index
return state.anchorPosition?.let { anchorPosition->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) ?:
state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Passenger> {
val position = params.key ?: STARTING_PAGE_INDEX
return when(val result = dataRepository.getPassengerListForPage(position, params.loadSize)){
is Resource.Failure -> LoadResult.Error(Exception(result.toString()))
is Resource.Loading -> LoadResult.Error(Exception())
is Resource.Success -> {
LoadResult.Page(data = result.data.passengerList,
prevKey = if(position == STARTING_PAGE_INDEX) null else -1,
nextKey = if (result.data.passengerList.isEmpty()) null else position + 1)
}

}
}
}

Now used the seal class to manage the response and other error and loading case.

sealed class Resource {
val data:PassengerListResponse = PassengerListResponse(100, 50, listOf<Passenger>())
class Failure : Resource()
class Loading : Resource()
class Success : Resource()
}

Step 4: Develop Data Source Factory

After creating the Datasource class, you need to implement Data Source Factory.

The data source Factory class is used to create data sources from the network.

class PassengerDataSourceFactory(private val dataRepository: DataRepository) {

val NETWORK_PAGE_SIZE = 10

fun getPassengers() = Pager(config = PagingConfig(pageSize = NETWORK_PAGE_SIZE, enablePlaceholders = false),
pagingSourceFactory = {PassengerItemDataSource(dataRepository)}).liveData
}

Step 5: Create List Adapter Class

Normally, RecyclerView uses RecyclerView.Adapter or ListAdapter but for Paging 3 we use PagingDataAdapter but the behaviour is like a normal adapter.

PagingDataAdapter takes a DiffUtil callback, as a parameter to its primary constructor which helps the PagingDataAdapter to update the items if they are changed or updated. And DiffUtil callback is used because they are more performant.

class PassengerListAdapter():PagingDataAdapter<Passenger,PassengerListAdapter.PassengerViewHolder>(PassengerListDiffCallback()) {

inner class PassengerViewHolder(private val rawPassengerItemBinding: RawPassengerItemBinding) :
RecyclerView.ViewHolder(rawPassengerItemBinding.root) {

fun bind(passenger: Passenger) {
//rawPassengerItemBinding.passenger = passenger
}
}

class PassengerListDiffCallback : DiffUtil.ItemCallback<Passenger>() {
override fun areItemsTheSame(oldItem: Passenger, newItem: Passenger) =
oldItem.id == newItem.id

override fun areContentsTheSame(
oldItem: Passenger,
newItem: Passenger
) = oldItem == newItem

override fun getChangePayload(oldItem: Passenger, newItem: Passenger): Any? {
if (oldItem != newItem) {
return newItem
}
return super.getChangePayload(oldItem, newItem)
}
}

override fun onBindViewHolder(holder: PassengerViewHolder, position: Int) {
val item: Passenger? = getItem(position)
item?.let {
holder.bind(item)
}
}

override fun onBindViewHolder(
holder: PassengerViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (payloads.isNullOrEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val newItem = payloads[0] as Passenger
holder.bind(newItem)
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PassengerViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = RawPassengerItemBinding.inflate(inflater)
return PassengerViewHolder(binding)
}
}

Step 6: Loading Adapter in fragment or Activity

Finally, we need to get the data from the PagingDataAdapter and set the data into the recyclerview. We will collect the data from the getMovieList() function inside the lifecycleScope and update the data we fetched from the API and display it in the UI.

lass MainActivity : AppCompatActivity() {

lateinit var viewModel: MainViewModel
lateinit var mainListAdapter: MainListAdapter

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

private fun setupView() {
lifecycleScope.launch {
viewModel.listData.collect {
mainListAdapter.submitData(it)
}
}
}

private fun setupList() {
mainListAdapter = MainListAdapter()
recyclerView.apply {
layoutManager = LinearLayoutManager(this)
adapter = mainListAdapter
}
}

private fun setupViewModel() {
viewModel =
ViewModelProvider(
this,
MainViewModelFactory(APIService.getApiService())
)[MainViewModel::class.java]
}
}

You can check the source code from here:

Next part will use of paging library with composed UI.

Thanks for the reading…

--

--

Abhishek Srivastava

Senior Software Engineer | Android | Java | Kotlin | Xamarin Native Android | Flutter | Go