Modern Android Pagination: Efficient List Rendering with Jetpack Paging 3

TL;DR: Jetpack Paging 3 makes it easy to load and display large data sets in Android apps efficiently. This guide covers setup, usage, best practices, and common pitfalls for high-performance pagination.


Cover Image

[ADD IMAGE - Cover]
Suggestion: A modern Android UI displaying a scrolling list with a loading spinner at the bottom.
Alt text: Android list with pagination using Jetpack Paging 3


Table of Contents

  1. Introduction to Jetpack Paging 3
  2. Why Use Paging 3?
  3. Setting Up Paging 3 in Your Project
  4. Implementing a PagingSource
  5. Integrating with ViewModel & Repository
  6. Displaying Data in RecyclerView
  7. Handling Loading & Error States
  8. Best Practices for Performance
  9. Common Mistakes to Avoid
  10. Conclusion & Further Reading

1) Introduction to Jetpack Paging 3

Loading large datasets in Android can easily lead to memory issues and laggy UIs. Jetpack Paging 3 solves this by loading data in chunks (pages), fetching more only when needed. It works with Room, Retrofit, and any custom data source.

[ADD IMAGE - Paging flow diagram]
Alt: Diagram showing Paging 3 data flow from data source to UI


2) Why Use Paging 3?

  • Asynchronous loading with Kotlin coroutines
  • Seamless integration with Flow and LiveData
  • Supports network + database caching (RemoteMediator)
  • Handles UI states (loading, error, retry) out of the box

Tip: Paging 3 can be combined with Room for offline-first apps.


3) Setting Up Paging 3 in Your Project

First, add dependencies to your build.gradle:

dependencies {
    implementation "androidx.paging:paging-runtime:3.3.0" // For Kotlin
    // or
    implementation "androidx.paging:paging-runtime-ktx:3.3.0"
}

4) Implementing a PagingSource

A PagingSource defines how data is loaded page-by-page.


class ArticlePagingSource(
    private val api: ArticleApi
) : PagingSource<Int, Article>() {

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

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

5) Integrating with ViewModel & Repository


class ArticleRepository(private val api: ArticleApi) {
    fun getArticles(): Pager<Int, Article> {
        return Pager(
            config = PagingConfig(pageSize = 20),
            pagingSourceFactory = { ArticlePagingSource(api) }
        )
    }
}

class ArticleViewModel(private val repository: ArticleRepository) : ViewModel() {
    val articles = repository.getArticles()
        .flow
        .cachedIn(viewModelScope)
}

6) Displaying Data in RecyclerView

Use PagingDataAdapter for RecyclerView integration.


class ArticleAdapter : PagingDataAdapter<Article, ArticleViewHolder>(DIFF_CALLBACK) {
    override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
        getItem(position)?.let { holder.bind(it) }
    }
    // ...
}

7) Handling Loading & Error States

Paging 3 offers LoadStateAdapter for displaying loading spinners and retry buttons.

Tip: Always provide a retry mechanism for network errors to improve UX.


8) Best Practices for Performance

  • Use cachedIn to retain paging data across configuration changes
  • Set prefetchDistance in PagingConfig to load data earlier
  • Combine RemoteMediator for offline + online data sync

9) Common Mistakes to Avoid

  • Loading all data at once instead of using paging
  • Not handling empty state UI
  • Blocking UI thread with heavy operations in bind

10) Conclusion & Further Reading

Jetpack Paging 3 simplifies pagination while maintaining performance and scalability. By following best practices and avoiding common mistakes, you can create smooth, responsive lists in your Android apps.

Further resources:


Appendix

References

  • Android Developers Docs
  • Kotlin Coroutines Guide

Note: This document is fully compatible with WYSIWYG editors.

This article was updated on August 15, 2025