分页库仅在房间数据库中存储第一次获取的值

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

以上是关于分页库仅在房间数据库中存储第一次获取的值的主要内容,如果未能解决你的问题,请参考以下文章

Android房间分页不起作用

使用分页库时,观察者显示列表大小为零

使用分页库显示加载指示器

分页库 3.0:如何将项目总数传递给列表标题?

使用 Paging 3 Android 从房间获取最新数据

Android官方架构组件Paging:分页库的设计美学