Pagination in Android with Paging 3

October 14, 2021 Publish By : EXPERT APP DEVS 6 min read Viewed By : 103
pagination in android with paging 3

Overview

Paging 3 concept introduced in the Android Jetpack component that helps you to display pages of data from a large number of data from local databases or remote data servers. By using the Paging 3 library, we can load a lot of data efficiently and seamlessly, only we will list or display the data based on user needs. The Paging 3 library is written entirely using Kotlin Coroutines.

Advantages

  • In-memory cache.
  • Kotlin low and LiveData support.
  • Error handling, refresh and retry capabilities.
  • Loading partial data based on user needs reduces the usages of network bandwidth and system resources.

Architecture

The Paging library can easily integrate with recommended Android app architecture. The library's components communicate with three layers of the app:

  1. Repository
  2. ViewModel
  3. View

Architecture Repository ViewModel View

Repository

In the Repository layer, there is the PagingSource. The PagingSource defines a data source and how to retrieve data from the data source. Also, we can load from a remote data source or local database.

In the Repository layer, there is the RemoteMediator. The RemoteMediator manages the paging from a layered data source.

ViewModel

The Pager component helps us to create instances of PagingData that are sent to the View. pager instance based on a PagingSource and a PagingConfig configuration.

View

Inside the View section, there is the PagingDataAdapter and a RecyclerView adapter that help you to display paginate data.

Implementation

dependencies {
     implementation "androidx.paging:paging-runtime:3.0.1"
}

Creating a Data Source

We can get data from the server and store it in the Room database to add an offline usage feature with a RemoteMediator. 


Let’s start with creating the response model.

Create a data class for our pagination data response from the server.


data class PagesDetailsDataResponse(
// serialize the page info data
    @SerializedName("info") val pageDetails: PageDetails,
// list of user data
    val results: List<Avatar> = listOf()
)

Create a data class as per below for the pagination data.

// data class for the page details
data class PageDetails(
// page count
    val pagecount: Int,
// next page 
    val nextpage: String,
// total pages
    val pages: Int,
// prev page 
    val prevpage: String?)

// data class for User Details

data class Avatar(
    val id: Int,
// usertype
    val userType: String,
// status
    val status: Status,
// user image
    val imageUrl: String,
// user full name
    val nameFullName: String,
// gender
   val gender: String)

Creating DataSource

class AvatarPagingDataSource(private val service: AvatarApi) :
// Paging Data Source
    PagingSource<Integer, Avatar>() {
   // override function for the loading data page by page
    suspend fun load(params: LoadParams<Integer>): LoadResult<Integer, Avatar> {
      // pass the default page index here in params key
        val currentPage= params.key?: 1

        return try {
    // call you api here for fetching data from the server
            val response = service.getAllAvatars(currentPage)

// get the pagedata source from response body
            val pagesDataResponse= response.body()

    // you can get your to list here that you can pass in LoadResult.Page
            val data = pagesDataResponse?.results

    // init next page number
            var nextPageNumber: Int? = null

    // below snippet for the next page details and next page Query
            if (pagesDataResponse?.pageDetails?.next != null) {
                val uri = Uri.parse(pagesDataResponse.pageDetails.next)
    // nextpage query parameter     
                val nextPageQuery = uri.getQueryParameter("page")
    // you will get nextpage number from the nextpagequery
                nextPageNumber = nextPageQuery?.toInt()
            }

    // you can pass your paginate data list here and the previous and next key for your pagination.

            LoadResult.Page(
// pass the list of data
                data = data.orEmpty(),
// previous page key
                prevKey = null,
// nextpage number/key
                nextKey = nextPageNumber
            )

// you can catch pagination exception in below catch block
        } catch (e: Exception) {
// catch exception here
            LoadResult.Error(e)
// print exception
    e.printstacktrace()
        }
    }
}

Pager

The source for the data returned from PagingSource is called PagingData. A Pager instance has to be created to build a stream of PagingData. Three things are needed to create an instance. These are:

  • PagingSource is our data source created with the name of AvatarPagingDataSource.
  • You need to provide details to PagingConfig on how to get data from Paging Source like page count, prefetch distance, etc. An optional initialKey to start loading with a default key of the paging.

val avatars: Flow<PagingData<Avatar>> = 
  Pager(config = PagingConfig(pageCount = 10, prefetchDistance = 3),
    pagingSourceFactory = { AvatarPagingDataSource(service) }
    ).flow.cachedIn(viewModelScope)

Connect RecyclerView to the PagingData

class AvatarAdapter @Inject constructor() :
    PagingDataAdapter<Avatar, AvatarAdapter .AvatarHolder>(AvatarComparator) {
    var avatarClickListener: AvatarClickListener? = null
    override fun onCreateViewHolder(parent: ViewGroup, viewtype: Int) =
        AvatarHolder(
            ItemAvatarBinding.inflate(
                LayoutInflater.from(parent.context), parent, false
            )
        )

    override fun onBindViewHolder(holder: AvatarHolder, position: Int) {
        getItem(position)?.let { holder.bind(it) }
    }

    inner class AvatarHolder(private val binding: ItemAvatarBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: Avatar) = with(binding) {
            avatar= item
        }
    }

    object AvatarComparator : DiffUtil.ItemCallback<Avatar>() {
        override fun areItemsTheSame(oldItem: Avatar, newItem: Avatar) =
            oldItem.id == newItem.id

        override fun areContentsTheSame(oldItem: Avatar, newItem: Avatar) =
            oldItem == newItem
    }
}

The final step displays data in our fragment or activity from the data source and sets it into the AvatarAdapter.

val avatarAdapter = AvatarAdapter()

rvAvatars.apply {
    layoutManager = LinearLayoutManager(context)
    adapter = avatarAdapter 
}

viewLifecycleOwner.lifecycleScope.launchWhenCreated {
  viewModel.avatarsFlow.collectLatest { pagingData ->
    avatarAdapter .submitData(pagingData)
  }
}

The RecyclerView or listview now displays data coming from the data container and loads the next pages when the user scrolls to the end of the list.

I hope you like the post if you have any queries related to android app development please contact us.

Need a consultation?

Drop us a line! We are here to answer your questions 24/7.