Search code examples
androidpaginationandroid-jetpack-composeandroid-jetpack-compose-material3android-native-library

Paging 3 Library with Jetpack Compose Not calling the load method after initial load


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() }
                                    )
                                }
                            }
                        })
                }
            }

Solution

  • 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() }
                                )
                            }
                        }
                    }
                )
            }
        }
    }