使用Remote Mediator时自定义Paging Source无法加载更多数据的解决方法
自定义Paging Source无法加载更多数据的问题
Room默认提供的Paging Source不符合需求,自行实现后出现以下问题:仅能加载前1-2页数据(取决于Remote Mediator的Refresh操作是否删除数据库数据),向下滚动时无法加载更多。推测核心原因是自定义Paging Source未感知到Remote Mediator从API下载并插入数据库的新数据,求解决办法。
自定义Paging Source代码
class TopFilmsLocalPagingSource( private val filmLocalStorage: FilmLocalStorage, private val type: TopFilmCategories): PagingSource<Int, Film>() { override fun getRefreshKey(state: PagingState<Int, Film>): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Film> { val currentPage = params.key ?: 1 val offset = (currentPage - 1) * params.loadSize val count = params.loadSize return try { val films = filmLocalStorage.getFilmsByType(offset, count, type) val prevKey = if (currentPage == 1) null else currentPage - 1 val nextKey = if (films.count() < count) null else currentPage + 1 LoadResult.Page(films, prevKey, nextKey) } catch (ex: Exception){ LoadResult.Error(ex) } } }
Remote Mediator代码(companion object中last字段用于测试)
@OptIn(ExperimentalPagingApi::class) class FilmRemoteMediator( private val filmLocalStorage: FilmLocalStorage, private val filmRemoteStorage: FilmRemoteStorage, private val type: TopFilmCategories): RemoteMediator<Int, Film>() { override suspend fun load(loadType: LoadType, state: PagingState<Int, Film>): MediatorResult { return try{ val loadKey = when (loadType) { LoadType.REFRESH -> 1 LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND -> { last += 1 last } } val films = when(type){ TopFilmCategories.TOP_100_POPULAR_FILMS -> filmRemoteStorage.getPopularFilms(loadKey) TopFilmCategories.TOP_250_BEST_FILMS -> filmRemoteStorage.getBestFilms(loadKey) TopFilmCategories.TOP_AWAIT_FILMS -> filmRemoteStorage.getTopAwaitFilms(loadKey) } if (loadType == LoadType.REFRESH) { filmLocalStorage.refreshFilmsByType(films, type) MediatorResult.Success(endOfPaginationReached = films.isEmpty()) } else { filmLocalStorage.insertAllFilms(films, type) MediatorResult.Success(endOfPaginationReached = films.isEmpty()) } } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } companion object{ var last = 1 } }
Repository代码
class FilmRepositoryImpl @Inject constructor( private val filmRemoteStorage: FilmRemoteStorage, private val filmLocalStorage: FilmLocalStorage): FilmRepository { @OptIn(ExperimentalPagingApi::class) override fun getBestFilmsPaged(): Flow<PagingData<DomainFilm>> { return Pager( PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 20), remoteMediator = FilmRemoteMediator( filmLocalStorage, filmRemoteStorage, TopFilmCategories.TOP_250_BEST_FILMS ) ) { TopFilmsLocalPagingSource(filmLocalStorage, TopFilmCategories.TOP_250_BEST_FILMS) }.flow.toDomain() } } fun Flow<PagingData<com.gramzin.cinescope.data.model.Film>>.toDomain(): Flow<PagingData<DomainFilm>> { return transform { value -> emit(value.map { it.toDomain() }) } }
操作日志
- paging source: load page 1
- remote mediator: refresh operation (load page 1)
- paging source: load page 2
- remote mediator: load success
- remote mediator: prepend operation
- remote mediator: append operation (load page 2)
- remote mediator: load success
问题分析与解决办法
1. 自定义Paging Source未监听数据库更新
Room自带的Paging Source会自动监听数据库变化并触发刷新,但自定义的TopFilmsLocalPagingSource无此机制。Remote Mediator插入新数据后,Paging Source无法感知,不会尝试加载新页码。
解决:
- 在
FilmLocalStorage中暴露监听对应类型电影数据变化的Flow,比如getFilmsByTypeFlow(type: TopFilmCategories): Flow<List<Film>>。 - 在
TopFilmsLocalPagingSource中收集该Flow,当数据变化时调用invalidate(),触发Paging Source重新加载。
2. Remote Mediator页码管理逻辑错误
用companion object的last字段管理页码是线程不安全的,且配置变化或重启APP会导致页码重置,同时未结合数据库实际数据量计算下一页。
解决:
- 移除全局
last变量,改为从数据库查询已加载的该类型电影总数,计算下一页:
LoadType.APPEND -> { val totalLoaded = filmLocalStorage.getTotalFilmsCount(type) (totalLoaded / state.config.pageSize) + 1 }
3. Paging Source的nextKey计算逻辑有误
当Remote Mediator插入数据前,Paging Source加载第2页时若数据量不足,会返回nextKey = null,后续即使数据更新也不会再尝试加载。
解决:
- 不要仅根据当前加载的数量判断是否有下一页,可结合API返回的总页数,或在Remote Mediator完成插入后主动调用
PagingSource.invalidate(),让Paging Source重新计算nextKey。
4. 验证Room分页查询正确性
检查filmLocalStorage.getFilmsByType(offset, count, type)是否正确实现了LIMIT count OFFSET offset,且排序逻辑与API一致,避免数据重复或遗漏。
内容的提问来源于stack exchange,提问作者ramzes1_1
相关产品推荐
相关产品推荐

