Paging 3 library for data pagination

Alex Chen Jan 2026
3 tabs
package com.example.myapp.data.paging

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.myapp.data.remote.ApiService
import com.example.myapp.models.Post

class PostPagingSource(
    private val apiService: ApiService,
    private val query: String? = null
) : PagingSource<Int, Post>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Post> {
        return try {
            val page = params.key ?: 1
            val perPage = params.loadSize

            val response = if (query != null) {
                apiService.searchPosts(query, page, perPage)
            } else {
                apiService.getPosts(page, perPage)
            }

            val posts = response.posts
            val nextPage = if (posts.isEmpty() || response.meta.page >= response.meta.totalPages) {
                null
            } else {
                page + 1
            }

            LoadResult.Page(
                data = posts,
                prevKey = if (page == 1) null else page - 1,
                nextKey = nextPage
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, Post>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
    }
}
3 files · kotlin Explain with highlit

Paging 3 loads large datasets incrementally with network and database support. I create a PagingSource implementing load() method to fetch pages. RemoteMediator orchestrates network and database, fetching from API and caching locally. Pager configuration sets page size and load strategies. The library returns Flow<PagingData> observed in UI. LoadState tracks loading, refreshing, and error states. cachedIn(viewModelScope) shares data across collectors. Separators inject headers with insertSeparators(). Retry mechanisms handle failed loads. Compose's LazyPagingItems or RecyclerView's PagingDataAdapter render paginated data. Paging eliminates manual page tracking and provides smooth infinite scrolling.