I am trying to use jetpack compose paging 3 library with start and limit, it loads the first items, but when I scroll trough the list it doesn't call the load method again.
I placed multiple logs, and I can see that the method is called just once, initially and then nothing happens after that. I really don't know what is wrong here. This is my code:
const val PAGE_SIZE = 30
class PhotosPagingSource (private val photosApi: PhotosApi) : PagingSource<Int, Photos>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Photos> {
return try {
val page = params.key ?: 0
val start = page * PAGE_SIZE
val limit = params.loadSize
val response = photosApi.getPhotos(start, limit)
val photos = PhotosMapper.mapPhotosDTO(response)
LoadResult.Page(
data = photos,
prevKey = if (page == 0) null else page - 1,
nextKey = if (response.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, Photos>): Int? {
return state.anchorPosition
}
}
class PhotosRepositoryImpl @Inject constructor(
private val photosApi: PhotosApi,
): PhotosRepository {
override suspend fun getPhotos(): Flow<PagingData<Photos>> {
return Pager(config = PagingConfig(pageSize = 30, prefetchDistance = 3 )) {
PhotosPagingSource(photosApi)
}.flow
}
}
@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: PhotosRepository
) : ViewModel() {
init {
getPhotos()
}
private var _pagingFlow: MutableStateFlow<PagingData<Photos>> =
MutableStateFlow(value = PagingData.empty())
val pagingFlow: StateFlow<PagingData<Photos>> = _pagingFlow
private fun getPhotos() {
viewModelScope.launch(Dispatchers.IO) {
repository.getPhotos()
.distinctUntilChanged()
.cachedIn(viewModelScope)
.collect {
_pagingFlow.value = it
}
}
}
}
val lazyPagingItems = homeViewModel.pagingFlow.collectAsLazyPagingItems()
val state = lazyPagingItems.loadState
val listState = rememberLazyListState()
val pagingData = lazyPagingItems.itemSnapshotList
when {
state.refresh is LoadState.Loading && pagingData.size == 0 -> {
ShimmerHome()
}
state.refresh is LoadState.Loading && pagingData.size == 0 -> {
LoadingComposable()
}
state.refresh is LoadState.Error -> {
TODO()
}
state.append is LoadState.Loading -> {
LoadingComposable()
}
state.append is LoadState.Error -> {
TODO()
}
else -> {
LazyColumn(
modifier = Modifier,
state = listState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
content = {
items(items = pagingData) { pagingData ->
if (pagingData != null) {
PhotosCard(
photo = pagingData,
onCardClicked = {TODO() }
)
}
}
})
}
}
In jetpack don't use itemSnapshotList
and work only with lazyPagingItems
. The thing is you have to use LazyPagingItems#get(index: Int)
to get item and also notify paging data about that you are getting close to end of the page, from docs:
Returns the presented item at the specified position, notifying Paging of the item access to trigger any loads necessary to fulfill prefetchDistance.
With itemSnapshotList
pager doesn't know that you scrolled to bottom and need load another page.
Updated code:
@Composable
fun PagingTest(homeViewModel: HomeViewModel = hiltViewModel()) {
val lazyPagingItems = homeViewModel.pagingFlow.collectAsLazyPagingItems()
val state = lazyPagingItems.loadState
val listState = rememberLazyListState()
when {
state.refresh is LoadState.Loading && lazyPagingItems.itemCount == 0 -> {
ShimmerHome()
}
state.refresh is LoadState.Loading && lazyPagingItems.itemCount == 0 -> {
LoadingComposable()
}
state.refresh is LoadState.Error -> {
TODO()
}
state.append is LoadState.Loading -> {
LoadingComposable()
}
state.append is LoadState.Error -> {
TODO()
}
else -> {
LazyColumn(
modifier = Modifier,
state = listState,
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
content = {
items(count = lazyPagingItems.itemCount) { index ->
lazyPagingItems.get(index = index)?.let { photo ->
PhotosCard(
photo = pagingData,
onCardClicked = { TODO() }
)
}
}
}
)
}
}
}