Dependecies
dependencies {
implementation "androidx.paging:paging-compose:1.0.0-alpha14"
}
import androidx.compose.foundation.lazy.LazyColumnimport androidx.compose.material.Textimport androidx.compose.runtime.Composableimport androidx.compose.ui.tooling.preview.Previewimport androidx.paging.*import androidx.paging.compose.collectAsLazyPagingItemsimport androidx.paging.compose.itemsimport kotlinx.coroutines.flow.Flowimport kotlin.random.Randomdata class Member(val name: String, val age: Int)class MemberRepository(val totalCount: Int, val pageSize: Int) { fun getMembers(page: Int): List<Member> { val random = Random(page) val startIndex = (page - 1) * pageSize + 1 var endIndex = startIndex + pageSize - 1 if (endIndex > totalCount) { endIndex = totalCount } return (startIndex..endIndex).map { index -> Member(name = "Member #${index}", age = random.nextInt(1, 99)) } }}class MemberSource(private val repo: MemberRepository) : PagingSource<Int, Member>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Member> { val page = params.key ?: 1 return LoadResult.Page( data = repo.getMembers(page), prevKey = if (page == 1) null else page - 1, nextKey = if (page * repo.pageSize < repo.totalCount) page + 1 else null ) } override fun getRefreshKey(state: PagingState<Int, Member>): Int? { return state.anchorPosition }}
ViewModel
class MemberViewModel: ViewModel() { val membersFlow = fetchMemberData() fun fetchMemberData(): Flow<PagingData<Member>> { val repo = MemberRepository(totalCount = 101, pageSize = 10) return Pager(PagingConfig(pageSize = repo.pageSize)) { MemberSource(repo) }.flow }}
Usage
@Composablefun MemberScreen(viewModel: MemberViewModel = viewModel()) { val lazyMembers = viewModel.membersFlow.collectAsLazyPagingItems() LazyColumn() { items(lazyMembers) { member -> if (member != null) { Text("${member.name}, age=${member.age}") } } }}@Preview@Composablefun MemberScreenPreview() { MemberScreen()}
Show Loading Indicator & Error/Retry Handling
@Composablefun MemberScreen() { val lazyMembers = fetchMemberData().collectAsLazyPagingItems() LazyColumn() { items(lazyMembers) { member -> if (member != null) { Text("${member.name}, age=${member.age}") } } val refreshState = lazyMembers.loadState.refresh if (refreshState is LoadState.Loading) item { Center(modifier = Modifier.fillParentMaxSize()) { Text("Initial data fetch ...") CircularProgressIndicator() } } else if (refreshState is LoadState.Error) item { Center(modifier = Modifier.fillParentMaxSize()) { val error = refreshState.error Text("Initial Data Error: ${error.localizedMessage}") Button(onClick = { lazyMembers.retry() }) { Text("Retry") } } } val appendState = lazyMembers.loadState.append if (appendState is LoadState.Loading) item { Row(verticalAlignment = Alignment.CenterVertically) { Text("Subsequent data fetch ...") CircularProgressIndicator() } } else if (appendState is LoadState.Error) item { val error = appendState.error Row(verticalAlignment = Alignment.CenterVertically) { Text("Subsequent data Error: ${error.localizedMessage}") Button(onClick = { lazyMembers.retry() }) { Text("Retry") } } } }}
Simulate delay and error
class MemberRepository(val totalCount: Int, val pageSize: Int) { suspend fun getMembers(page: Int): List<Member> { val random = Random(page) val startIndex = (page - 1) * pageSize + 1 var endIndex = startIndex + pageSize - 1 if (endIndex > totalCount) { endIndex = totalCount } delay(1000) if (Random.nextBoolean()) throw Exception("Test Error") return (startIndex..endIndex).map { index -> Member(name = "Member #${index}", age = random.nextInt(1, 99)) } }}class MemberSource(private val repo: MemberRepository) : PagingSource<Int, Member>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Member> { val page = params.key ?: 1 return try { LoadResult.Page( data = repo.getMembers(page), prevKey = if (page == 1) null else page - 1, nextKey = if (page * repo.pageSize < repo.totalCount) page + 1 else null ) } catch (e: Exception) { LoadResult.Error(e) } } override fun getRefreshKey(state: PagingState<Int, Member>): Int? { return state.anchorPosition }}
References