Search code examples
androidandroid-jetpack-composeandroid-paging-3android-jetpack-compose-lazy-column

Refreshing LazyPagingItems without visibly refreshing LazyColumn in Jetpack Compose


I'm using Jetpack Compose and LazyPagingItems to display paged data in a LazyColumn using the Paging 3 library. I have implemented the refresh functionality when the user comes back to the Tab by calling refresh() on the PagingSource.

However, when I call refresh() on the PagingSource associated with LazyPagingItems, the entire LazyColumn refreshes, causing the UI to visibly reload and scroll back to the top. This behavior is not ideal as it disrupts the user experience. I just need the column to update with the new data if there is any.

I want to find a way to refresh the data in LazyPagingItems without letting the user notice any visible changes. In other words, I want to update the data silently in the background.

@HiltViewModel
class MainViewModel @Inject constructor(
    private val beersRepository: BeersRepository,
) : ViewModel() {

    val state: Flow<PagingData<Beer>> = beersRepository
        .getBeers()
        .flow
        .cachedIn(viewModelScope)

}

//Repository
class BeersRepository @Inject constructor(
    private val beersApiService: BeersApiService,
) {

    fun getBeers() = Pager(
        config = PagingConfig(
            pageSize = 10,
            enablePlaceholders = true,
        ),
        pagingSourceFactory = { BeersPagingSource(beersApiService = beersApiService) }
    )
}

//Paging source
class BeersPagingSource @Inject constructor(
    private val beersApiService: BeersApiService,
) : PagingSource<Int, Beer>() {

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

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Beer> {
        val page = params.key ?: 1
        return try {
            delay(500)
            val response = beersApiService.getBeers(page, perPage = 10)
            if (response.isSuccessful) {
                val beers = response.body()!!.map {
                    it.toDomain()
                }
                LoadResult.Page(
                    data = beers,
                    prevKey = if (page == 1) null else page - 1,
                    nextKey = if (beers.isEmpty()) null else page + 1
                )
            } else {
                LoadResult.Error(Exception(response.message()))
            }
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}


//And finally the Composable
private fun MainScreenRoute(
    viewModel: MainViewModel = viewModel(),
) {
    val pagingItems = viewModel.state.collectAsLazyPagingItems()

    Scaffold(bottomBar = {
        Row(
            modifier = Modifier
                .background(Color.Transparent)
                .fillMaxWidth(),
            horizontalArrangement = Arrangement.Center
        ) {
            Button(onClick = {
                pagingItems.refresh()
            }) {
                Text(
                    text = "Refresh", style = MaterialTheme.typography.bodyLarge
                )
            }
        }
    }) {
        Surface {
            Column(
                modifier = Modifier.fillMaxSize()
            ) {
                when (pagingItems.loadState.refresh) {
                    is LoadState.Error -> {

                    }

                    LoadState.Loading -> {
                        Surface {
                            Box(
                                contentAlignment = Alignment.Center,
                                modifier = Modifier.fillMaxSize()
                            ) {
                                Text(
                                    text = "Loading...",
                                    style = MaterialTheme.typography.headlineLarge
                                )
                            }
                        }
                    }

                    is LoadState.NotLoading -> {
                        LazyColumn(
                            verticalArrangement = Arrangement.spacedBy(8.dp),
                            contentPadding = PaddingValues(8.dp)
                        ) {
                            items(pagingItems.itemCount) { index ->
                                val beer = pagingItems[index]!!
                                BeerCard(
                                    imageUrl = beer.image,
                                    name = beer.name,
                                    description = beer.description,
                                )
                            }
                        }
                    }
                }

            }
        }
    }
}

Attempted refresh() on LazyPagingItems.


Solution

  • Have you cached your giftsList in your ViewModel? It should look something like that:

    val giftsList = repository.giftsList.cachedIn(viewModelScope)