8

Android Jetpack Paging 3.0 Tutorial – Paged RecyclerView using Retrofit

 3 years ago
source link: https://www.simplifiedcoding.net/android-jetpack-paging-3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android Jetpack Paging 3.0 Tutorial – Paged RecyclerView using Retrofit

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.

Announcement: Now you can join Simplified Coding Space at Quora. You can ask any query here related to my tutorial or android development. 

What is Paging?

If you are already into Android Development, then I am pretty sure that you know about RecyclerView.

Why do we use RecyclerView? 

We use it to display list of data. And this list in some cases can be huge, in-fact so huge. Consider any OTT application, you see a list of movies or media files; and this list may contain thousands of items.

Will you fetch all these thousands of items at once and create view objects to display all items at once? Of course a BIG NO.

It is a very bad idea to fetch 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.

So an efficient solution is divide your large data set into small pages, and fetch them page by page.

Jetpack released Paging Library long back, and I have already published a complete guide about Android Paging Library here.

But now we have Paging 3.0. And it is really really better than the previous version. And the best part is Kotlin Coroutines, Flows or RxJava is officially supported now.

In this tutorial I will teach you, how you can create a paged list using paging 3.0 with your backend api.

Backend API

In most cases we fetch data from a Backend API. And for this tutorial I’ve selected this free JSON API.

https://api.instantwebtools.net/v1/passenger?page=0&size=10

Here you can see in the API we are passing page index and page size. It is a dummy API and you can use it as well.

Creating a new Project

Now, let’s start a new project to implement Jetpack Paging 3.0.

I have created a new project using an Empty Activity, and with Kotlin Language.

  • Come inside project level build.gradle  and add the following classpath.
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
  • Now come inside app level build.gradle  file and add these two plugins.
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs"
  • Now add compile options of Java8 and enable ViewBinding.
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    viewBinding {
        enabled true
  • Finally add these dependencies and sync the project.
    //Kotlin Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    //New Material Design
    implementation 'com.google.android.material:material:1.3.0-alpha02'
    //Android Navigation Architecture
    implementation "androidx.navigation:navigation-fragment-ktx:2.3.0"
    implementation "androidx.navigation:navigation-ui-ktx:2.3.0"
    //OKHttp Interceptor
    implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"
    // Android Jetpack Paging 3.0
    implementation "androidx.paging:paging-runtime:3.0.0-alpha06"
    //Glide to load images from URL
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
  • And our project is setiup.

Setting Up Retrofit

Now let’s setup our Retrofit that will hit the backend api to fetch data sets.

Creating API Interface

  • Create an interface named MyApi.kt  and write the following code.
interface MyApi {
    @GET("passenger")
    suspend fun getPassengersData(
        @Query("page") page: Int,
        @Query("size") size: Int = 10
    ): PassengersResponse
    companion object {
        private const val BASE_URL = "https://api.instantwebtools.net/v1/"
        operator fun invoke(): MyApi = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(OkHttpClient.Builder().also { client ->
                val logging = HttpLoggingInterceptor()
                logging.setLevel(HttpLoggingInterceptor.Level.BODY)
                client.addInterceptor(logging)
            }.build())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(MyApi::class.java)
  • As you can see I have defined a function to hit our GET API, and inside companion object I have the invoke function that will return us our API Instance.
  • You will get an error in this file for now, because we have not created the model data classes.

Data Classes to Map API Response

  • I use a plugin to directly create data classes for JSON Structures. You can check here, to know more about the plugin.
  • The classes that we have to map the API Response are
data class PassengersResponse(
    val `data`: List<Passenger>,
    val totalPages: Int,
    val totalPassengers: Int
data class Passenger(
    val __v: Int,
    val _id: String,
    val airline: Airline,
    val name: String,
    val trips: Int
data class Airline(
    val country: String,
    val established: String,
    val head_quaters: String,
    val id: Int,
    val logo: String,
    val name: String,
    val slogan: String,
    val website: String
  • So we have the above 3 classes, and you need to make three different files for these three classes.

Fragment to display the Paged List

Now let’s first create an Empty Fragment. Here I have created a fragment named PassengersFragment.

Inside the layout of this fragment we will create our RecyclerView.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.PassengersFragment">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/item_passenger" />
</FrameLayout>

We also need a layout file for our RecyclerView, and for this I have created a file named item_passenger.xml .

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">
    <ImageView
        android:id="@+id/image_view_airlines_logo"
        android:layout_width="280dp"
        android:layout_height="52dp"
        android:layout_gravity="center_horizontal"
        tools:background="@drawable/luthfansa" />
    <TextView
        android:id="@+id/text_view_headquarters"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="12dp"
        android:textAlignment="center"
        android:textColor="#000000"
        android:textSize="14sp"
        tools:text="Jom Phol Subdistrict, Chatuchak, Bangkok, Thailand" />
    <TextView
        android:id="@+id/text_view_name_with_trips"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:gravity="center_horizontal"
        android:textAlignment="center"
        android:textColor="#000000"
        android:textSize="22sp"
        tools:text="Dodi Papagena, 2223 Trips" />
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="8dp"
        android:alpha="0.3"
        android:background="#000000" />
</LinearLayout>

Navigation Graph

  • As we are also using Navigation Architecture, we need to create a navigation graph.
  • I have created nav_graph_main.xml .
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph_main"
    app:startDestination="@id/passengersFragment">
    <fragment
        android:id="@+id/passengersFragment"
        android:name="net.simplifiedcoding.ui.PassengersFragment"
        android:label="passengers_fragment"
        tools:layout="@layout/passengers_fragment" />
</navigation>

MainActivity

  • Now come inside acitivity_main.xml  and define the NavHostFragment.
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph_main"
    app:startDestination="@id/passengersFragment">
    <fragment
        android:id="@+id/passengersFragment"
        android:name="net.simplifiedcoding.ui.PassengersFragment"
        android:label="passengers_fragment"
        tools:layout="@layout/passengers_fragment" />
</navigation>
  • Here we have added our fragment, and it is also the start destination.

Creating Utils

  • Create a file named Utils.kt  and write the following code in it.
fun View.visible(isVisible: Boolean) {
    visibility = if (isVisible) View.VISIBLE else View.GONE
fun ImageView.loadImage(url: String) {
    Glide.with(this)
        .load(url)
        .into(this)

Here we have created two extension functions, one is to change the visibility of a View, and another one is to load image into an ImageView using Glide.

PagingDataAdapter

To display a paged list in RecyclerView, we need to create a PagingDataAdapter.

  • I have created a class named PassengersAdapter  to create my PagingDataAdapter.
class PassengersAdapter :
    PagingDataAdapter<Passenger, PassengersAdapter.PassengersViewHolder>(PassengersComparator) {
    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): PassengersViewHolder {
        return PassengersViewHolder(
            ItemPassengerBinding.inflate(
                LayoutInflater.from(parent.context), parent, false
    override fun onBindViewHolder(holder: PassengersViewHolder, position: Int) {
        val item = getItem(position)
        item?.let { holder.bindPassenger(it) }
    inner class PassengersViewHolder(private val binding: ItemPassengerBinding) :
        RecyclerView.ViewHolder(binding.root) {
        @SuppressLint("SetTextI18n")
        fun bindPassenger(item: Passenger) = with(binding) {
            imageViewAirlinesLogo.loadImage(item.airline.logo)
            textViewHeadquarters.text = item.airline.head_quaters
            textViewNameWithTrips.text = "${item.name}, ${item.trips} Trips"
    object PassengersComparator : DiffUtil.ItemCallback<Passenger>() {
        override fun areItemsTheSame(oldItem: Passenger, newItem: Passenger): Boolean {
            return oldItem._id == newItem._id
        override fun areContentsTheSame(oldItem: Passenger, newItem: Passenger): Boolean {
            return oldItem == newItem

PagingSource

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

class PassengersDataSource(
    private val api: MyApi
) : PagingSource<Int, Passenger>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Passenger> {
        return try {
            val nextPageNumber = params.key ?: 0
            val response = api.getPassengersData(nextPageNumber)
            LoadResult.Page(
                data = response.data,
                prevKey = if (nextPageNumber > 0) nextPageNumber - 1 else null,
                nextKey = if (nextPageNumber < response.totalPages) nextPageNumber + 1 else null
        } catch (e: Exception) {
            LoadResult.Error(e)

Creating ViewModel

Now let’s create a ViewModel  for our Fragment. Inside ViewModel  we will get our PagingSource to get the paged data.

class PassengersViewModel(
    private val api: MyApi
) : ViewModel() {
    val passengers = Pager(PagingConfig(pageSize = 10)) {
        PassengersDataSource(api)
    }.flow.cachedIn(viewModelScope)

That’s it, here we will get the passengers using Flow, and it will create a stream for us, that we can use to get paged data in our Fragment or Activity.

As we are passing a parameter inside our ViewModel, we also need to create a ViewModelFactory .

@Suppress("UNCHECKED_CAST")
class PassengersViewModelFactory(
    private val api: MyApi
) : ViewModelProvider.NewInstanceFactory(){
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return PassengersViewModel(api) as T

Now, that’s it we are ready to display the paged data in RecyclerView.

Displaying Paged RecyclerView

Come inside your fragment, in this case it is PassengersFragment .

class PassengersFragment : Fragment() {
    private lateinit var viewModel: PassengersViewModel
    private lateinit var binding: PassengersFragmentBinding
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ) = PassengersFragmentBinding.inflate(inflater, container, false).also {
        binding = it
    }.root
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        val factory = PassengersViewModelFactory(MyApi())
        viewModel = ViewModelProvider(this, factory).get(PassengersViewModel::class.java)
        val passengersAdapter = PassengersAdapter()
        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
        binding.recyclerView.setHasFixedSize(true)
        binding.recyclerView.adapter = passengersAdapter
        lifecycleScope.launch {
            viewModel.passengers.collectLatest { pagedData ->
                passengersAdapter.submitData(pagedData)

Now you can run your application and you will see your paged list.

Android Jetpack Paging 3 Library
Android Jetpack Paging 3 Library

But wait, we are not done yet, we need to also show, loading state, error message and a retry button.

Displaying Loading/Error State

First we need a layout for our Loading/Error state.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_gravity="center_horizontal" />
    <TextView
        android:id="@+id/text_view_error"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textAlignment="center"
        android:textColor="#700101"
        android:textSize="14sp"
        android:visibility="gone"
        tools:text="Some Error Occurred"
        tools:visibility="visible" />
    <TextView
        android:id="@+id/button_retry"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="12dp"
        android:text="Tap to Retry"
        android:textAllCaps="false"
        android:textColor="#122278"
        android:textSize="16sp" />
</LinearLayout>

Now we will create one more Adapter that is the LoadStateAdapter.

LoadStateAdapter

  • I have created a new class for my LoadStateAdapter.
class PassengersLoadStateAdapter(
    private val retry: () -> Unit
) : LoadStateAdapter<PassengersLoadStateAdapter.PassengerLoadStateViewHolder>() {
    inner class PassengerLoadStateViewHolder(
        private val binding: ItemLoadingStateBinding,
        private val retry: () -> Unit
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(loadState: LoadState) {
            if (loadState is LoadState.Error) {
                binding.textViewError.text = loadState.error.localizedMessage
            binding.progressbar.visible(loadState is LoadState.Loading)
            binding.buttonRetry.visible(loadState is LoadState.Error)
            binding.textViewError.visible(loadState is LoadState.Error)
            binding.buttonRetry.setOnClickListener {
                retry()
    override fun onBindViewHolder(holder: PassengerLoadStateViewHolder, loadState: LoadState) {
        holder.bind(loadState)
    override fun onCreateViewHolder(
        parent: ViewGroup,
        loadState: LoadState
    ) = PassengerLoadStateViewHolder(
        ItemLoadingStateBinding.inflate(LayoutInflater.from(parent.context), parent, false),
        retry

Using LoadStateAdapter

Now we will use the function withLoadStateHeaderAndFooter  to add the LoadStateAdapter  to our PagedAdapter .

        binding.recyclerView.adapter = passengersAdapter.withLoadStateHeaderAndFooter(
            header = PassengersLoadStateAdapter { passengersAdapter.retry() },
            footer = PassengersLoadStateAdapter { passengersAdapter.retry() }

And that’s it. Run your app and you will see the loading state or error state if occurred, you will also get a retry button and everything will work smoothly; as you can see here.

Android Jetpack Paging 3.0 Source Code

You can also download my source code, if you are having any kind of trouble following the tutorial.

Android Jetpack Paging 3.0 Source Code Download

So that is all for this post friends. In case you have any problem related to this tutorial, feel free to comment it below. And I will try to help you out. Very soon I will post a detailed Video Tutorial about this post, so that you can understand the concept more easily. So make sure you have Subscribed to Simplified Coding’s YouTube Channel. You can also SHARE this post and the channel with your friends. Thank You 🙂

Hi, my name is Belal Khan and I am a Google Developers Expert (GDE) for Android. The passion of teaching made me create this blog. If you are an Android Developer, or you are learning about Android Development, then I can help you a lot with Simplified Coding.

Checkout these tutorials as well:

  • Android Networking Tutorial with Retrofit, PHP and MySQL
  • Android ViewModel using Retrofit - ✅ A Simple Tutorial
  • Android Data Binding Library Tutorial with Firebase
  • Android Push Notification Tutorial using FCM HTTP V1
  • Kotlin Coroutines Tutorial for Android - Start using Coroutines in Your App
  • Direct Reply Notification in Android like WhatsApp Tutorial

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK