Paging 3 Android Tutorial
Parth

Parth @parthprajapatispan

About: Web & Mobile Dev Expert | Vue.js, React, WordPress, Flutter, Bubble. Building beautiful & functional experiences across platforms.

Joined:
Jul 4, 2024

Paging 3 Android Tutorial

Publish Date: Jun 26
0 0

In today’s dynamic mobile development ecosystem, optimizing the user experience with smooth data loading is critical. Android’s Paging 3 library — part of Android Jetpack — empowers developers to efficiently load paginated data from various sources. Whether you're pulling data from a local database, a REST API, or a hybrid source, Paging 3 ensures better memory usage and seamless scrolling.

In this tutorial, we will walk you through each step required to implement Paging 3 in your Android application. This guide is suitable for intermediate developers, and by the end, you’ll have a fully functional paginated list with headers, footers, and reactive programming support through RxJava.

Let’s dive into the ultimate Paging 3 Android tutorial.

Introduction to Paging 3 Library
The Paging 3 library is designed to handle large datasets efficiently by loading them incrementally, reducing memory consumption, and offering a smoother UI experience. Unlike previous versions, Paging 3 is built on Kotlin coroutines and Flow but also supports RxJava and LiveData.

Its robust architecture ensures easy integration with Room, Retrofit, and remote mediators, offering out-of-the-box support for complex pagination needs.

Understanding and Implementing Paging 3 Library
Let's break down how you can implement the Paging 3 library from scratch.

Step 01. Add Dependencies
To get started, add the following dependencies in your app-level build.gradle file:

dependencies {
    implementation "androidx.paging:paging-runtime:3.2.1"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
}
Enter fullscreen mode Exit fullscreen mode

For RxJava support, include:

implementation "androidx.paging:paging-rxjava3:3.2.1"
Enter fullscreen mode Exit fullscreen mode

Sync your project to proceed.

Step 02. Create Data Models
Assume you’re fetching user data from an API. Here's a sample data model:

data class User(
    val id: Int,
    val name: String,
    val email: String
)
Enter fullscreen mode Exit fullscreen mode

Also, define the API response wrapper if required.

Step 03. Create a PagingSource
PagingSource is responsible for loading pages of data. Create a class that extends PagingSource:

class UserPagingSource(
    private val apiService: ApiService
) : PagingSource<Int, User>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
        val page = params.key ?: 1
        return try {
            val response = apiService.getUsers(page)
            LoadResult.Page(
                data = response.users,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.users.isEmpty()) null else page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, User>): Int? {
        return state.anchorPosition?.let { anchor ->
            state.closestPageToPosition(anchor)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchor)?.nextKey?.minus(1)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 04. Create a Pager and Repository
Create a repository that provides PagingData using a Pager object.

class UserRepository(private val apiService: ApiService) {

    fun getUserStream(): Flow<PagingData<User>> {
        return Pager(
            config = PagingConfig(pageSize = 20),
            pagingSourceFactory = { UserPagingSource(apiService) }
        ).flow
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 05. Set up ViewModel with PagingData
Integrate the repository into your ViewModel.

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    val users: Flow<PagingData<User>> = repository.getUserStream()
        .cachedIn(viewModelScope)
}
Enter fullscreen mode Exit fullscreen mode

cachedIn ensures the paging data survives configuration changes like screen rotation.

Step 06. Implement Adapter and ViewHolder
Create a PagingDataAdapter and corresponding ViewHolder.

class UserAdapter : PagingDataAdapter<User, UserAdapter.UserViewHolder>(DIFF_CALLBACK) {

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position)
        holder.bind(user)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(user: User?) {
            itemView.findViewById<TextView>(R.id.userName).text = user?.name
        }
    }

    companion object {
        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
            override fun areItemsTheSame(oldItem: User, newItem: User) = oldItem.id == newItem.id
            override fun areContentsTheSame(oldItem: User, newItem: User) = oldItem == newItem
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 07. Connect Everything in the UI
Now, bind your adapter in the Activity or Fragment.

lifecycleScope.launch {
    viewModel.users.collectLatest {
        adapter.submitData(it)
    }
}
Enter fullscreen mode Exit fullscreen mode

Getting the States of the Data

adapter.addLoadStateListener { loadState ->
    if (loadState.refresh is LoadState.Loading) {
        // Show loading spinner
    } else if (loadState.refresh is LoadState.Error) {
        // Show error message
    } else {
        // Hide spinner
    }
}
Enter fullscreen mode Exit fullscreen mode

This enables you to provide feedback to users and handle empty states gracefully.

Adding the Header and Footer View

Paging 3 allows you to add headers and footers using LoadStateAdapter.

val adapter = UserAdapter()
    .withLoadStateHeaderAndFooter(
        header = LoadingStateAdapter { adapter.retry() },
        footer = LoadingStateAdapter { adapter.retry() }
    )
Enter fullscreen mode Exit fullscreen mode

This is ideal for showing a retry button on failure or a loading indicator when fetching more data.

Using it with RxJava

If your architecture is RxJava-centric, Paging 3 offers full support for RxJava3.

Step 01. Add RxJava Support
Ensure the following dependency is included:

implementation "androidx.paging:paging-rxjava3:3.2.1"
Enter fullscreen mode Exit fullscreen mode

Step 02. Create RxPagingSource
Instead of Flow, return a Flowable<PagingData<T>>

class RxUserRepository(private val apiService: ApiService) {

    fun getUserStream(): Flowable<PagingData<User>> {
        return RxPager(
            config = PagingConfig(pageSize = 20),
            pagingSourceFactory = { UserPagingSource(apiService) }
        ).flowable
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 03. Setup Rx Pager Flowable
Integrate it into your ViewModel:

val userStream: Flowable<PagingData<User>> = repository.getUserStream()
    .cachedIn(viewModelScope)
Enter fullscreen mode Exit fullscreen mode

Step 04. Bind Data in UI
Subscribe to the RxJava stream in the Activity:

userViewModel.userStream
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { pagingData ->
        adapter.submitData(lifecycle, pagingData)
    }
Enter fullscreen mode Exit fullscreen mode

Make sure to manage the disposables properly to avoid memory leaks.

Conclusion
Paging 3 significantly improves data pagination in Android apps by offering coroutine, Flow, LiveData, and RxJava support. It ensures optimal performance even when dealing with large or frequently updating datasets. From creating a PagingSource to binding data in the UI, this tutorial covered everything you need to implement Paging 3 effectively.

If your team is planning to scale your Android development efforts or implement robust, enterprise-grade features like Paging 3, it might be time to hire Android developers who bring advanced knowledge and architectural discipline to your projects.

Unlocking high performance in your apps begins with the right tools — and the right team behind them.

Comments 0 total

    Add comment