Jetpack Compose Paging Sample

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

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.