kotlin
187 lines · 3 tabs
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)
}
}
}
package com.example.myapp.data.paging
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapp.data.local.AppDatabase
import com.example.myapp.data.local.PostEntity
import com.example.myapp.data.local.RemoteKeys
import com.example.myapp.data.remote.ApiService
@OptIn(ExperimentalPagingApi::class)
class PostRemoteMediator(
private val apiService: ApiService,
private val database: AppDatabase
) : RemoteMediator<Int, PostEntity>() {
private val postDao = database.postDao()
private val remoteKeysDao = database.remoteKeysDao()
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, PostEntity>
): MediatorResult {
return try {
val page = when (loadType) {
LoadType.REFRESH -> 1
LoadType.PREPEND -> return MediatorResult.Success(
endOfPaginationReached = true
)
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey ?: return MediatorResult.Success(
endOfPaginationReached = remoteKeys != null
)
}
}
val response = apiService.getPosts(page, state.config.pageSize)
val posts = response.posts
val endOfPaginationReached = posts.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
postDao.deleteAll()
remoteKeysDao.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = posts.map {
RemoteKeys(
postId = it.id,
prevKey = prevKey,
nextKey = nextKey
)
}
remoteKeysDao.insertAll(keys)
postDao.insertAll(posts.map { it.toEntity() })
}
MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (e: Exception) {
MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyForLastItem(
state: PagingState<Int, PostEntity>
): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { post ->
remoteKeysDao.getRemoteKeys(post.id)
}
}
}
package com.example.myapp.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@HiltViewModel
class PagingViewModel @Inject constructor(
private val apiService: ApiService,
private val database: AppDatabase
) : ViewModel() {
val postsFlow: Flow<PagingData<Post>> = Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false,
prefetchDistance = 5,
initialLoadSize = 40
),
remoteMediator = PostRemoteMediator(apiService, database),
pagingSourceFactory = { database.postDao().observePostsPaging() }
).flow.cachedIn(viewModelScope)
fun searchPosts(query: String): Flow<PagingData<Post>> = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { PostPagingSource(apiService, query) }
).flow.cachedIn(viewModelScope)
}
// In Composable:
@Composable
fun PostsList(viewModel: PagingViewModel = hiltViewModel()) {
val posts = viewModel.postsFlow.collectAsLazyPagingItems()
LazyColumn {
items(posts.itemCount) { index ->
posts[index]?.let { post ->
PostCard(post = post)
}
}
posts.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { LoadingItem() }
}
loadState.append is LoadState.Loading -> {
item { LoadingItem() }
}
loadState.refresh is LoadState.Error -> {
item { ErrorItem { posts.retry() } }
}
loadState.append is LoadState.Error -> {
item { ErrorItem { posts.retry() } }
}
}
}
}
}
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.
Related snips
kotlin
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
Dependency injection with Hilt
kotlin
android
hilt
by Alex Chen
3 tabs
kotlin
package com.example.myapp.ui
import androidx.lifecycle.*
import kotlinx.coroutines.launch
class UserViewModel(
LiveData transformations and mediators
kotlin
android
livedata
by Alex Chen
1 tab
kotlin
package com.example.myapp.utils
import android.os.Build
import android.os.StrictMode
import android.os.Trace
import timber.log.Timber
Performance optimization and profiling
kotlin
android
performance
by Alex Chen
2 tabs
kotlin
package com.example.myapp.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
App widgets for home screen
kotlin
android
widget
by Alex Chen
2 tabs
kotlin
package com.example.myapp.data.repository
import com.example.myapp.data.local.PostDao
import com.example.myapp.data.local.PostEntity
import com.example.myapp.data.remote.ApiService
import com.example.myapp.models.Post
Unit testing with JUnit and MockK
kotlin
android
testing
by Alex Chen
2 tabs
kotlin
package com.example.myapp.utils
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.app.PendingIntent
Notification channels and categories
kotlin
android
notifications
by Alex Chen
1 tab
Share this code
Here's the card — post it anywhere.