【发布时间】: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观察吗?另外,你有没有接到RemoteMediatorAPPEND/PREPEND的电话? -
是的,我正在使用 collectLatest ,对于 append 和 prepend ,我认为它们只被调用一次,我对分页库 3 不太熟悉,但我已经放置了一个日志,我将数据推送到房间在附加部分,仅第一次调用(我的意思是当前 20 部电影被加载时)
-
我注意到您有两个寻呼机,一个用于离线,一个用于在线,这对我来说似乎不正确。 Paging 中的所有内容都由 PagingSource 驱动,因此您不需要两者。 RemoteMediator 基本上是一个回调 - 如果您想使用离线数据,您可以简单地尝试远程刷新时的网络获取,并且只有在成功时才清除 + 插入。
-
如果您可以在这里使用
Flow<PagingData>分享您是如何混合多个 / 的,我可以尝试提供更多帮助,但实际上信息还不够。 -
其实我也刚刚注意到,在您使用 RemoteMediator 的“在线”版本中,您的
pagingSourceFactory也不同。MoviePagingSource()是什么样的?您应该使用 Room 提供的那个,因为您要插入 Room 并使用它来驱动 Paging。
标签: android kotlin paging android-paging-library