Jetpack Compose中Paging3 Remote Mediator无滚动无限调用API的修复方法
问题分析与修复方案
你的代码出现无限调用API的问题,核心原因有两个:分页粒度错误和RemoteMediator分页逻辑漏洞,以下是具体修复步骤:
1. 修正分页粒度(最关键问题)
你当前把整个API响应(TopAnimeResponse,包含整页数据和分页信息)作为Paging的条目存在数据库中,但Pager的pageSize=5,这会让Paging误认为只加载了1条数据(因为每个TopAnimeResponse是1个条目),远小于pageSize,因此会持续触发APPEND加载,导致无限循环。
修复方案:
- 新建单个动漫实体类(比如
Anime),对应API响应中data数组的每个元素。 - 修改数据库Dao,改为存储和查询
Anime实体,而非TopAnimeResponse。 - 调整RemoteMediator的泛型为
RemoteMediator<Int, Anime>,加载API后将response.data列表插入数据库。
2. 修复RemoteMediator的分页逻辑漏洞
当前APPEND分支中,lastItem?.pagination?.currentPage?.plus(1)可能返回null,如果API调用时page参数默认被设为1,会导致每次都重复请求第一页,进而触发无限加载。同时,分页状态的跟踪需要更可靠:
修正后的RemoteMediator示例:
@OptIn(ExperimentalPagingApi::class) class TopAnimeRemoteMediator( private val apiService: ApiService, private val database: TopAnimeDatabase, ) : RemoteMediator<Int, Anime>() { // 泛型改为单个Anime实体 override suspend fun load( loadType: LoadType, state: PagingState<Int, Anime> ): MediatorResult { val page = when (loadType) { LoadType.REFRESH -> 1 LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND -> { // 从数据库获取最后一条Anime的页码(需要给Anime实体添加currentPage字段) val lastPage = database.animeDao().getLastPageNumber() if (lastPage == null) { return MediatorResult.Success(endOfPaginationReached = true) } // 读取存储的分页状态(需单独建表存储hasNextPage信息) val hasNextPage = database.paginationDao().getHasNextPage() if (!hasNextPage) { return MediatorResult.Success(endOfPaginationReached = true) } lastPage + 1 } } return try { val response = apiService.getTopAnime("g", page, PAGE_SIZE_CONFIG) database.withTransaction { if (loadType == LoadType.REFRESH) { database.animeDao().deleteAllAnime() database.paginationDao().clear() } // 给每个Anime标记当前页码,用于后续分页判断 val animeList = response.data.map { it.copy(currentPage = page) } database.animeDao().insertAnime(animeList) // 存储当前页的分页状态 database.paginationDao().insert(PaginationInfo(page, response.pagination.hasNextPage)) } MediatorResult.Success(endOfPaginationReached = !response.pagination.hasNextPage) } catch (e: Exception) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } companion object { const val PAGE_SIZE_CONFIG = 5 } }
3. 修正Compose View的Paging用法
当前View中直接遍历itemSnapshotList.items的写法会绕过Paging的滚动加载检测,导致Paging无法正确判断是否需要加载下一页。正确用法是直接绑定LazyPagingItems:
修正后的LazyVerticalStaggeredGrid:
LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(200.dp), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalItemSpacing = 2.dp, modifier = modifier.fillMaxSize() ) { // 直接使用items绑定LazyPagingItems items(topAnimeList) { anime -> anime?.let { TopAnimeItem(it) } } // 添加加载状态提示(可选) topAnimeList.apply { when (loadState.append) { is LoadState.Loading -> { item { Box( modifier = Modifier.fillMaxWidth().padding(16.dp), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } } is LoadState.Error -> { item { TextButton(onClick = { retry() }) { Text("加载失败,点击重试") } } } else -> {} } } }
4. 其他辅助修正
- 确保Repository中的Pager
pageSize与API每页返回的data数组长度一致(即5)。 - 保留ViewModel中
cachedIn(viewModelScope)的用法,避免重复创建Flow导致的重复请求。
内容的提问来源于stack exchange,提问作者Ridhwanafzn
相关产品推荐
相关产品推荐

