ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Jetpack Compose and collectAsLazyPagingItems

Dobri Kostadinov
ProAndroidDev
Published in
6 min readAug 26, 2024

This image was generated with the assistance of AI

Introduction

What is LazyColumn?

LazyColumn {
items(listOfItems) { item ->
Text(text = item.title)
}
}

Introducing the Paging Library

collectAsLazyPagingItems: A Seamless Integration

@Composable
fun MyPagingScreen(viewModel: MyViewModel = viewModel()) {

val lazyPagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems()

LazyColumn {
items(lazyPagingItems) { item ->
item?.let {
MyListItem(it)
}
}
}
}

How It Works

@Composable
fun MyPagingScreen(viewModel: MyViewModel = viewModel()) {
val lazyPagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems()

when (lazyPagingItems.loadState.refresh) {
is LoadState.Loading -> {
CircularProgressIndicator()
}
is LoadState.Error -> {
Text("An error occurred")
}
else -> {
LazyColumn {
items(lazyPagingItems) { item ->
item?.let {
MyListItem(it)
}
}
}
}
}
}

Performance Optimizations

Customizing the Behavior

1. Customizing Item Layout

@Composable
fun MyListItem(data: MyData) {
Row(modifier = Modifier.padding(8.dp)) {
Image(painter = rememberImagePainter(data.imageUrl), contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = data.title, style = MaterialTheme.typography.h6)
Text(text = data.description, style = MaterialTheme.typography.body2)
}
}
}

2. Handling Load States with Custom UI

@Composable
fun MyPagingScreen(viewModel: MyViewModel = viewModel()) {
val lazyPagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems()

when (lazyPagingItems.loadState.refresh) {
is LoadState.Loading -> {
CircularProgressIndicator(modifier = Modifier.fillMaxSize())
}
is LoadState.Error -> {
Text(
text = "An error occurred. Please try again.",
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center
)
}
else -> {
LazyColumn {
items(lazyPagingItems) { item ->
item?.let {
MyListItem(it)
}
}
lazyPagingItems.apply {
when {
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator() }
}
loadState.append is LoadState.Error -> {
item {
Text(
text = "Failed to load more items.",
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
}
}
}
}

3. Adding a Footer or Header

@Composable
fun MyPagingScreen(viewModel: MyViewModel = viewModel()) {
val lazyPagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems()

LazyColumn {
item {
Text("List Header", style = MaterialTheme.typography.h5, modifier = Modifier.padding(8.dp))
}
items(lazyPagingItems) { item ->
item?.let {
MyListItem(it)
}
}
lazyPagingItems.apply {
when (loadState.append) {
is LoadState.Loading -> {
item {
CircularProgressIndicator(modifier = Modifier.padding(8.dp))
}
}
is LoadState.Error -> {
item {
Text(
text = "Failed to load more items.",
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth().padding(8.dp)
)
}
}
else -> {}
}
}
item {
Text("List Footer", style = MaterialTheme.typography.h5, modifier = Modifier.padding(8.dp))
}
}
}

4. Pre-fetching Data

val pagingConfig = PagingConfig(
pageSize = 20,
prefetchDistance = 5, // Pre-fetch the next page when 5 items away from the end
initialLoadSize = 40 // Initial load size
)

val pagingDataFlow: Flow<PagingData<MyData>> = Pager(pagingConfig) {
MyPagingSource()
}.flow.cachedIn(viewModelScope)

5. Custom Paging Source Logic

class MyPagingSource : PagingSource<Int, MyData>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MyData> {
return try {
val currentPage = params.key ?: 1
val data = fetchDataFromSource(page = currentPage)

if (data.isEmpty()) {
LoadResult.Error(NoDataException("No data available"))
} else {
LoadResult.Page(
data = data,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (data.isEmpty()) null else currentPage + 1
)
}
} catch (e: Exception) {
LoadResult.Error(e)
}
}

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

6. Differentiating Items in LazyColumn

LazyColumn {
items(lazyPagingItems, key = { item -> item.id }) { item ->
when (item) {
is MyDataTypeA -> MyTypeAItem(item)
is MyDataTypeB -> MyTypeBItem(item)
}
}
}

Conclusion

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Dobri Kostadinov

15+ years in native Android dev (Java, Kotlin). Expert in developing beautiful android native apps.

No responses yet

Write a response