Android Paging Library using Kotlin Example

In this post, you can learn how to use new jetpack android paging library in your android application using kotlin language. Android Paging library is specifically used for data pagination when fetched from api.  You can check out sample app at HERE.

The android paging library helps you load and display small amounts of data at once. Loading partial data on demand reduces network bandwidth and system resource usage.
 
In this post, We use retrofit library to fetch data from api. We also use mvvm architecture pattern to handle data. If you are new to retrofit library and mvvm architecture then you can refer this article.
 

Add the below dependencies in your app level build.gradle file

apply plugin: 'kotlin-kapt'

android {
    dataBinding{
        enabled true
    }
}

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.android.support:recyclerview-v7:28.1.1'
    implementation 'com.android.support:cardview-v7:28.1.1'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
    implementation "android.arch.paging:runtime:1.0.1"
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
}
 
 

Create a Data Source class for page keyed content

There are three types of data source in jetpack paging library. You can use one of them according to your needs.
 
PageKeyedDataSource: If you load PageKeyedDataSource pages, they embed the next / previous keys. For example, if you are fetching users from the network, you have to pass the next load to the next load to the subsequent load.
 
ItemKeyedDataSource: If you need to get data N + 1 from item N, use ItemKeyedDataSource. For example, if you are fetching users from the network, you must pass the ID of the last user to get the content of the next user.
 
PositionalDataSource: If you need to fetch pages of data from any location in your data store, use PositionalDataSource. This class supports requesting a set of data items starting at any location you choose. For example, the request may return 50 data items starting with a location.
 
package com.example.paginglibrarysample.datasources

import android.content.Context
import android.util.Log
import androidx.paging.PageKeyedDataSource
import com.example.paginglibrarysample.api.ApiClient
import com.example.paginglibrarysample.models.User
import com.example.paginglibrarysample.models.UserResponse
import com.example.paginglibrarysample.utils.Utility
import com.example.paginglibrarysample.utils.Utility.isInternetAvailable
import com.example.paginglibrarysample.utils.Utility.showProgressBar
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class UserDataSource(private val context: Context) : PageKeyedDataSource<Int, User>() {

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, User>) {
        if (context.isInternetAvailable()) {
            getUsers(callback)
        }
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
        if (context.isInternetAvailable()) {
            val nextPageNo = params.key + 1
            getMoreUsers(params.key, nextPageNo, callback)
        }
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
        if (context.isInternetAvailable()) {
            val previousPageNo = if (params.key > 1) params.key - 1 else 0
            getMoreUsers(params.key, previousPageNo, callback)
        }
    }

    private fun getUsers(callback: LoadInitialCallback<Int, User>) {

        context.showProgressBar()

        ApiClient.apiService.getUsers(1).enqueue(object : Callback<UserResponse> {
            override fun onFailure(call: Call<UserResponse>, t: Throwable) {
                Utility.hideProgressBar()
            }

            override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
                Utility.hideProgressBar()
                val userResponse = response.body()
                val listUsers = userResponse?.listUsers
                listUsers?.let { callback.onResult(it, null, 2) }
            }

        })

    }

    private fun getMoreUsers(pageNo: Int, previousOrNextPageNo: Int, callback: LoadCallback<Int, User>) {

        ApiClient.apiService.getUsers(pageNo).enqueue(object : Callback<UserResponse> {
            override fun onFailure(call: Call<UserResponse>, t: Throwable) {

            }

            override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
                val userResponse = response.body()
                val listUsers = userResponse?.listUsers
                listUsers?.let { callback.onResult(it, previousOrNextPageNo) }
            }

        })

    }

}
 
 

Create a DataSource.Factory class for DataSource

package com.example.paginglibrarysample.datasourcesfactories

import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import androidx.paging.PagedList
import com.example.paginglibrarysample.datasources.UserDataSource
import com.example.paginglibrarysample.models.User

class UserDataSourceFactory(private val context: Context) : DataSource.Factory<Int, User>() {

    val mutableLiveData = MutableLiveData<UserDataSource>()

    override fun create(): DataSource<Int, User> {
        val userDataSource = UserDataSource(context)
        mutableLiveData.postValue(userDataSource)
        return userDataSource
    }

}
 

 

Create ApiService interface to define API methods

package com.example.paginglibrarysample.api

import com.example.paginglibrarysample.models.User
import com.example.paginglibrarysample.models.UserResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiService {

    @GET("users")
    fun getUsers(@Query ("page") page:Int): Call<UserResponse>

}
 
 

 

Create ApiClient class to call API

package com.example.paginglibrarysample.api

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory


object ApiClient {

    private const val BASE_URL: String = "https://reqres.in/api/"

    private val gson : Gson by lazy {
        GsonBuilder().setLenient().create()
    }

    private val interceptor : HttpLoggingInterceptor by lazy {
        HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
    }

    private val httpClient : OkHttpClient by lazy {
        OkHttpClient.Builder().addInterceptor(interceptor).build()
    }

    private val retrofit : Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(httpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
    }

    val apiService :  ApiService by lazy{
        retrofit.create(ApiService::class.java)
    }

}
 
 
 

Create Response class to parse json data fetched from API

package com.example.paginglibrarysample.models

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class UserResponse(

    @SerializedName("data")
    val listUsers : ArrayList<User>

) : Serializable

package com.example.paginglibrarysample.models

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class User(

    @SerializedName("id")
    val id: Int,

    @SerializedName("email")
    val email: String,

    @SerializedName("first_name")
    val firstName: String,

    @SerializedName("last_name")
    val lastName: String

) : Serializable
 
 
 

Create Util class to define functions for api calling

package com.example.paginglibrarysample.utils

import android.R
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.net.ConnectivityManager
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast

object Utility {

    private var progressBar: ProgressBar? = null

    fun Context.isInternetAvailable(): Boolean {
        try {
            val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val netInfo = cm.activeNetworkInfo
            return if (netInfo != null &amp;&amp; netInfo.isConnected) {
                true
            } else {
                showErrorToast("Internet not available. Please try again!!")
                false
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    fun Context.showErrorToast(message: String?) {

        try {
            val toast = Toast.makeText(this, message, Toast.LENGTH_LONG)

            // set message color
            val textView = toast.view.findViewById(android.R.id.message) as? TextView
            textView?.setTextColor(Color.WHITE)
            textView?.gravity = Gravity.CENTER

            // set background color
            toast.view.setBackgroundColor(Color.RED)

            toast.setGravity(Gravity.TOP or Gravity.FILL_HORIZONTAL, 0, 0)

            toast.show()
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    // show progressbar
    fun Context.showProgressBar() {
        try {
                val layout = (this as? Activity)?.findViewById<View>(android.R.id.content)?.rootView as? ViewGroup

                progressBar = ProgressBar(this, null, R.attr.progressBarStyleLarge)
                progressBar?.let { it1 ->
                    it1.isIndeterminate = true

                    val params = RelativeLayout.LayoutParams(
                        RelativeLayout.LayoutParams.MATCH_PARENT,
                        RelativeLayout.LayoutParams.MATCH_PARENT
                    )

                    val rl = RelativeLayout(this)

                    rl.gravity = Gravity.CENTER
                    rl.addView(it1)

                    layout?.addView(rl, params)

                    it1.visibility = View.VISIBLE
                }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // hide progressbar
    fun hideProgressBar() {
        try {
            progressBar?.let {
                it.visibility = View.GONE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

}
 
 
 

Create a ViewModel class to handle data

Paging configuration parameters

Page size: The number of items in each page.

Prefetch distance:&nbspGiven the last visible item in an app’s UI, the number beyond this last item that the paging library should try to fetch first. This value must be several times larger than the page size.

Placeholder presence: Determines whether the UI displays a placeholder for list items that have not yet finished loading.

package com.example.paginglibrarysample.viewmodels

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.example.paginglibrarysample.datasources.UserDataSource
import com.example.paginglibrarysample.datasourcesfactories.UserDataSourceFactory
import com.example.paginglibrarysample.models.User

class UserViewModel(private val context: Context) : ViewModel() {

    private var listUsers : LiveData<PagedList<User>> = MutableLiveData<PagedList<User>>()
    private var mutableLiveData = MutableLiveData<UserDataSource>()

    init {
        val factory : UserDataSourceFactory by lazy {
            UserDataSourceFactory(context)
        }
        mutableLiveData = factory.mutableLiveData

        val config = PagedList.Config.Builder()
            .setEnablePlaceholders(false)
            .setPageSize(6)
            .build()

        listUsers = LivePagedListBuilder(factory, config)
            .build()

    }

    fun getData() : LiveData<PagedList<User>>{
        return listUsers
    }


}
 
 
 

Create a ViewModelFactory class to pass custom arguments with ViewModel

package com.example.paginglibrarysample.viewmodelsfactories

import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.paginglibrarysample.viewmodels.UserViewModel

class UserViewModelFactory(private val context: Context) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return UserViewModel(context) as T
    }

}
 
 
 

Create layout for RecyclerView in Activity

<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_main"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
 
 
 

Create layout row for list

<?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">

    <data>
    <variable
        name="user"
        type="com.example.paginglibrarysample.models.User" />
    </data>

    <androidx.cardview.widget.CardView
        app:cardCornerRadius="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="15dp">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp">

            <TextView
                android:id="@+id/txt_user_name"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="22sp"
                android:textStyle="bold"
                android:text="@{user.firstName + ` ` + user.lastName}"/>

            <TextView
                android:id="@+id/txt_user_email"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/txt_user_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="16sp"
                android:text="@{user.email}"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>
</layout>
 
 

Create a PagedListAdapter class to display data from PagedList

package com.example.paginglibrarysample.adapters

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.paginglibrarysample.R
import com.example.paginglibrarysample.databinding.UserRowBinding
import com.example.paginglibrarysample.models.User

class UsersAdapter(private val context: Context) : PagedListAdapter<User,UsersAdapter.MyViewHolder>(USER_COMPARATOR) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(context)
        val binding: UserRowBinding = DataBindingUtil.inflate(inflater, R.layout.user_row,parent,false)
        return MyViewHolder(binding)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.itemBinding.user = getItem(position)
    }

    class MyViewHolder(val itemBinding: UserRowBinding) : RecyclerView.ViewHolder(itemBinding.root){

        private var binding : UserRowBinding? = null

        init {
            this.binding = itemBinding
        }

    }
    companion object {
        private val USER_COMPARATOR = object : DiffUtil.ItemCallback<User>() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean =
                oldItem.id == newItem.id
            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean =
                newItem == oldItem
        }
    }

}
 
 
 

Make changes in Activity

package com.example.paginglibrarysample.activities

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.paginglibrarysample.R
import com.example.paginglibrarysample.adapters.UsersAdapter
import com.example.paginglibrarysample.databinding.ActivityMainBinding
import com.example.paginglibrarysample.models.User
import com.example.paginglibrarysample.viewmodels.UserViewModel
import com.example.paginglibrarysample.viewmodelsfactories.UserViewModelFactory
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this@MainActivity, R.layout.activity_main)
        binding.recyclerMain.layoutManager = LinearLayoutManager(this@MainActivity)

        recycler_main.layoutManager = LinearLayoutManager(this@MainActivity)
        val adapter = UsersAdapter(this)
        recycler_main.adapter = adapter

        val userViewModel = ViewModelProvider(this,UserViewModelFactory(this)).get(UserViewModel::class.java)
        userViewModel.getData().observe(this, object : Observer<PagedList<User>>{
            override fun onChanged(t: PagedList<User>?) {
                adapter.submitList(t)
            }
        })
    }
}
 
 
 
android paging library
 
 
 

4 thoughts on “Android Paging Library using Kotlin Example

Leave a Reply