Paging 3, no more items loading when reaching the bottom of the list

66 Views Asked by At

I am creating an app, where the data will be fetched from an api, cached locally, and provided to my UI using paging 3. The weird thing is, when I reach the bottom of my list, no more items are being loaded.

The logs indicate that the next page (page 2) is called, so I really cannot understand why. I tried changing the pageSize to pageConfig, but to no avail.

@OptIn(ExperimentalPagingApi::class)
class GeneralRemoteMediator(
    private val apiService: ApiService,
    private val database: GeneralDatabase
) : RemoteMediator<Int, GeneralEntity>() {

    override suspend fun load(loadType: LoadType, state: PagingState<Int, GeneralEntity>): MediatorResult {
        try {
            val loadKey = when (loadType) {
                LoadType.REFRESH -> 1
                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
                LoadType.APPEND -> {
                    val lastItem = state.lastItemOrNull() ?: return MediatorResult.Success(endOfPaginationReached = true)
                    lastItem.page + 1
                }
            }

            val response = apiService.getPopularItems(loadKey)
            database.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    database.generalDao().clearAll()
                }
                val entities = response.results.map { it.toGeneralEntity(loadKey) }
                database.generalDao().insertAll(entities)
            }
            return MediatorResult.Success(endOfPaginationReached = response.results.isEmpty())
        } catch (e: Exception) {
            return MediatorResult.Error(e)
        }
    }
}
class GeneralPagingSource(
    private val dao: GeneralDao
) : PagingSource<Int, GeneralEntity>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GeneralEntity> {
        try {
            val pageNumber = params.key ?: 1
            val items = dao.getItemsByPage(pageNumber)
            return LoadResult.Page(
                data = items,
                prevKey = if (pageNumber == 1) null else pageNumber - 1,
                nextKey = if (items.isEmpty()) null else pageNumber + 1
            )
        } catch (e: Exception) {
            return LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, GeneralEntity>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
    }
}
class GeneralRepositoryImpl @Inject constructor(
    private val apiService: ApiService,
    private val database: GeneralDatabase
) : GeneralRepository {

    @OptIn(ExperimentalPagingApi::class)
    override fun getItems(): Flow<PagingData<GeneralItem>> {
        val pagingConfig = PagingConfig(pageSize = 20, initialLoadSize = 40, enablePlaceholders = false)

        return Pager(
            config = pagingConfig,
            remoteMediator = GeneralRemoteMediator(apiService, database),
            pagingSourceFactory = { GeneralPagingSource(database.generalDao()) }
        ).flow.flowOn(Dispatchers.IO).map { pagingData ->
            pagingData.map { it.toGeneralItem() }
        }
    }
}
class GetAllItemsUseCase @Inject constructor(
    private val repository: GeneralRepository
) {
    fun getItems(): Flow<PagingData<GeneralItem>> {
        return repository.getItems()
    }
}
@HiltViewModel
class GeneralViewModel @Inject constructor(
    private val getAllItemsUseCase: GetAllItemsUseCase
) : ViewModel() {
    val itemsFlow: Flow<PagingData<GeneralItem>> = getAllItemsUseCase.getItems().cachedIn(viewModelScope)
}

@Composable
fun AllItemsScreen(
    navController: NavController,
    viewModel: AllItemsViewModel = hiltViewModel()
) {
    val items = viewModel.itemsFlow.collectAsLazyPagingItems()

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        items(items.itemCount) { index ->
            items[index]?.let { item ->
                val imagePainter = rememberAsyncImagePainter(
                    model = ImageRequest.Builder(LocalContext.current)
                        .data(data = item.imageUrl ?: item.imageFromLocal)
                        .build()
                )
                ItemCard(
                    itemImagePainter = imagePainter,
                    itemTitle = item.title,
                    itemReleaseDate = item.releaseDate,
                    rating = item.rating.toFloat(),
                    isFavorite = item.isFavorite,
                    onFavoriteChange = {
                        // Implement favorite change logic here
                    }
                )
            }
        }

        items.apply {
            when {
                loadState.append is LoadState.Loading -> {
                    item { LoadingItem() }
                }
                loadState.refresh is LoadState.Error -> {
                    val e = items.loadState.refresh as LoadState.Error
                    item { ErrorItem(message = e.error.localizedMessage ?: "Unknown Error", onClickRetry = { retry() }) }
                }
                loadState.append.endOfPaginationReached -> {
                    item {
                        Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth().padding(16.dp)) {
                            Text("You've reached the end of the list")
                        }
                    }
                }
            }
        }
    }
}

@Composable
fun LoadingItem() {
    Box(modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp), contentAlignment = Alignment.Center) {
        CircularProgressIndicator()
    }
}

@Composable
fun ErrorItem(message: String, onClickRetry: () -> Unit) {
    Column(modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = message)
        Button(onClick = { onClickRetry() }) {
            Text("Retry")
        }
    }
}

@Composable
fun ItemCard(
    itemImagePainter: Painter,
    itemTitle: String,
    itemReleaseDate: String,
    rating: Float,
    isFavorite: Boolean,
    onFavoriteChange: () -> Unit
) {
    // Implementation for item card
}
0

There are 0 best solutions below