分页库仅在房间数据库中存储第一次获取的值
Posted
技术标签:
【中文标题】分页库仅在房间数据库中存储第一次获取的值【英文标题】:Paging library storing only first fetched values in room database 【发布时间】:2021-08-06 02:48:38 【问题描述】:我有一个应用程序,我在其中实现了分页库 3 以从 api 获取数据并对其进行分页,它可以很好地获取数据,下一个实现是将获取的数据存储在房间数据库中,我创建了 remotemediator 类并编写了存储数据的代码,但问题是它只存储第一页的值(例如,在我使用电影数据库 api 的情况下,获取的每个页面有 20 部电影,并且有很多页面),在我的情况下它只保存前 20 部电影,即使当我滚动时,它也没有存储更多数据,我已经实现了相同的确切代码,但似乎是这样,我在一个较旧的项目中遇到它,现在这个,我需要一些帮助,谢谢提前。
电影道 @Dao
interface MoviesDao
@Query("SELECT * FROM movieTable ORDER BY id")
fun getMovies() : PagingSource<Int,Result>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMovies(result: List<Result>)
@Query("DELETE FROM movieTable")
suspend fun clearMovies()
RemoteKeys 道
@Dao
interface RemoteKeysDao
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)
@Query("SELECT * FROM remote_keys WHERE movieId = :movieId")
suspend fun remoteKeysRepoId(movieId : Long): RemoteKeys?
@Query("DELETE FROM remote_keys")
suspend fun clearRemoteKeys()
RemoteMediator 类
private var MOVIES_API_STARTING_PAGE_INDEX = 1
@ExperimentalPagingApi
class MoviesMediator(
private var authResponse: AuthResponse,
private var movieDatabase: MovieDatabase
) : RemoteMediator<Int,Result>()
override suspend fun load(loadType: LoadType, state: PagingState<Int, Result>): MediatorResult
val page = when (loadType)
LoadType.REFRESH ->
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: MOVIES_API_STARTING_PAGE_INDEX
LoadType.PREPEND ->
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevKey = remoteKeys?.prevKey
if (prevKey == null)
return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
prevKey
LoadType.APPEND ->
val remoteKeys = getRemoteKeyForLastItem(state)
val nextKey = remoteKeys?.nextKey
if (nextKey == null)
return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
nextKey
try
val response = authResponse.getMovies(Constants.API_KEY, Constants.LANGUAGE, page).results
val endOfPagination = response.isEmpty()
movieDatabase.withTransaction
// clear all tables in the database
if (loadType == LoadType.REFRESH)
movieDatabase.remoteKeysDao().clearRemoteKeys()
movieDatabase.MovieDao().clearMovies()
val prevKey = if (page == MOVIES_API_STARTING_PAGE_INDEX) null else page - 1
val nextKey = if (endOfPagination) null else page + 1
val keys = response.map
RemoteKeys(movieId = it.movieID, prevKey = prevKey, nextKey = nextKey)
movieDatabase.remoteKeysDao().insertAll(keys)
movieDatabase.MovieDao().insertMovies(response)
return MediatorResult.Success(endOfPaginationReached = endOfPagination)
catch (ex: Exception)
return MediatorResult.Error(ex)
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Result>): RemoteKeys?
// Get the last page that was retrieved, that contained items.
// From that last page, get the last item
return state.pages.firstOrNull() it.data.isNotEmpty() ?.data?.firstOrNull()
?.let movieId ->
// Get the remote keys of the last item retrieved
movieDatabase.remoteKeysDao().remoteKeysRepoId(movieId.movieID)
private suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState<Int, Result>): RemoteKeys?
// The paging library is trying to load data after the anchor position
// Get the item closest to the anchor position
return state.anchorPosition?.let position ->
state.closestItemToPosition(position)?.movieID?.let movieId ->
movieDatabase.remoteKeysDao().remoteKeysRepoId(movieId = movieId)
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Result>): RemoteKeys?
// Get the last page that was retrieved, that contained items.
// From that last page, get the last item
return state.pages.lastOrNull() it.data.isNotEmpty() ?.data?.lastOrNull()
?.let repo ->
// Get the remote keys of the last item retrieved
movieDatabase.remoteKeysDao().remoteKeysRepoId(movieId = repo.movieID)
将 RemoteMediator 传递给分页数据
val dataFlow : kotlinx.coroutines.flow.Flow<PagingData<Result>> =
Pager(getPagingConfig(),
remoteMediator = MoviesMediator(authResponse,movieDatabase))
MoviePagingSource(authResponse)
.flow
.cachedIn(viewModelScope)
在 MainActivity 中显示数据
@ExperimentalPagingApi
private fun setUpAdapterOnline()
moviesAdapter = MoviesAdapter()
lifecycleScope.launchWhenStarted
moviesModel.dataFlow.collectLatest
moviesAdapter.submitData(it)
binding.recycler.adapter = moviesAdapter
binding.recycler.adapter = moviesAdapter.withLoadStateHeaderAndFooter(
header = LoadingStateAdapter moviesAdapter.retry() ,
footer = LoadingStateAdapter moviesAdapter.retry()
)
【问题讨论】:
您能否分享一下您是如何使用Flow<PagingData<Result>>
的?你在用collectLatest
观察吗?另外,你有没有接到RemoteMediator
APPEND
/PREPEND
的电话?
是的,我正在使用 collectLatest ,对于 append 和 prepend ,我认为它们只被调用一次,我对分页库 3 不太熟悉,但我已经放置了一个日志,我将数据推送到房间在附加部分,仅第一次调用(我的意思是当前 20 部电影被加载时)
我注意到您有两个寻呼机,一个用于离线,一个用于在线,这对我来说似乎不正确。 Paging 中的所有内容都由 PagingSource 驱动,因此您不需要两者。 RemoteMediator 基本上是一个回调 - 如果您想使用离线数据,您可以简单地尝试远程刷新时的网络获取,并且只有在成功时才清除 + 插入。
如果您可以在这里使用Flow<PagingData>
分享您是如何混合多个 / 的,我可以尝试提供更多帮助,但实际上信息还不够。
其实我也刚刚注意到,在您使用 RemoteMediator 的“在线”版本中,您的 pagingSourceFactory
也不同。 MoviePagingSource()
是什么样的?您应该使用 Room 提供的那个,因为您要插入 Room 并使用它来驱动 Paging。
【参考方案1】:
更新
我尝试使用 this NewsApi
而不是 this Unsplash Api
。即使NewsApi
提供per_page
作为查询参数,我仍然遇到类似的问题,即仅加载first 2
页面,然后在调试时显示Load
方法内的APPEND
块被调用一遍又一遍,分配给 page
的值始终为 2。因此,next pages
根本没有被获取。
原始答案
@dlam 是对的,这里需要遵循Single source of truth 的原则,只向MoviesDao
里面的Paging Source
请求数据。我没有找到您的问题的确切解决方案,但我确实有一些发现要分享。
我会说我在示例代码实验室here 中调试了Remote Mediator
,我发现它实际上会在您滚动时增加page
。但是,当从您发送给我的 github 链接调试代码时,page
的值永远不会增加,MoviesMediator
中的 APPEND
块会一遍又一遍地执行,即使不滚动也是如此。我试图找到原因,我唯一能想到的就是您正在使用的Api
。我尝试了相同的功能,从network
保存到database
但使用Unsplash Api
并且我也能够在离线时查看所有页面(不像你的情况只有一页)。
我不确定是否是代码实验室在Network/Api Service
接口内也作为argument
传递的per_page
参数。就我而言,Unsplash Api
也允许将per_page
作为参数传递。
我在这里附上link to my github repository。尝试比较结果,如有任何疑问,请随时提出。
【讨论】:
api 可能有问题,但是使用分页库 2 我可以保存多个页面,所以我不太确定为什么 我链接的项目也可以使用 Paging 3 保存多个页面。看看:)以上是关于分页库仅在房间数据库中存储第一次获取的值的主要内容,如果未能解决你的问题,请参考以下文章