分页库仅在房间数据库中存储第一个获取的值 [英] Paging library storing only first fetched values in room database

查看:90
本文介绍了分页库仅在房间数据库中存储第一个获取的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,我在其中实现了分页库 3 以从 api 获取数据并对其进行分页,它可以很好地获取数据,下一个实现是将获取的数据存储在房间数据库中,我已经创建了 remotemediator 类并编写了存储数据的代码,但问题是它只存储第一页的值(例如在我的情况下我使用电影数据库 api,获取的每个页面有 20 部电影,并且有很多页面),在我的情况下它只保存前 20 部电影,即使当我滚动时,它也没有存储更多数据,我已经实现了完全相同的代码,但似乎确实如此,我在一个较旧的项目中遇到了它,现在这个项目,我需要一些帮助,谢谢提前.

  • 电影道

 @Dao接口 MoviesDao {@Query(SELECT * FROM movieTable ORDER BY id")fun getMovies() : PagingSource@Insert(onConflict = OnConflictStrategy.REPLACE)暂停乐趣插入电影(结果:列表<结果>)@Query(从电影表中删除")暂停乐趣 clearMovies()}

  • RemoteKeys Dao

@Dao接口 RemoteKeysDao {@Insert(onConflict = OnConflictStrategy.REPLACE)暂停乐趣 insertAll(remoteKey: List)@Query(SELECT * FROM remote_keys WHERE movieId = :movieId")暂停乐趣 remoteKeysRepoId(movieId : Long): RemoteKeys?@Query(从远程密钥中删除")暂停乐趣 clearRemoteKeys()}

  • RemoteMediator 类

私有变量 MOVIES_API_STARTING_PAGE_INDEX = 1@ExperimentalPagingApi类 MoviesMediator(私有变量 authResponse: AuthResponse,私有变量电影数据库:电影数据库) : RemoteMediator() {覆盖挂起乐趣负载(loadType:LoadType,状态:PagingState):MediatorResult {val 页面 = 当 (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?.prevKeyif (prevKey == null) {返回 MediatorResult.Success(endOfPaginationReached = remoteKeys != null)}上一个密钥}LoadType.APPEND ->{val remoteKeys = getRemoteKeyForLastItem(state)val nextKey = remoteKeys?.nextKeyif (nextKey == null) {返回 MediatorResult.Success(endOfPaginationReached = remoteKeys != null)}下一个键}}尝试 {val response = authResponse.getMovies(Constants.API_KEY, Constants.LANGUAGE, page).resultsval endOfPagination = response.isEmpty()电影数据库.withTransaction {//清除数据库中的所有表if (loadType == LoadType.REFRESH) {movieDatabase.remoteKeysDao().clearRemoteKeys()电影数据库.MovieDao().clearMovies()}val prevKey = if (page == MOVIES_API_STARTING_PAGE_INDEX) null else page - 1val nextKey = if (endOfPagination) null else page + 1val 键 = response.map {RemoteKeys(movieId = it.movi​​eID, prevKey = prevKey, nextKey = nextKey)}movieDatabase.remoteKeysDao().insertAll(keys)movieDatabase.MovieDao().insertMovies(response)}return MediatorResult.Success(endOfPaginationReached = endOfPagination)} 捕获(例如:异常){返回 MediatorResult.Error(ex)}}private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys?{//获取检索到的最后一页,其中包含项目.//从最后一页,获取最后一项返回 state.pages.firstOrNull() { it.data.isNotEmpty() }?.data?.firstOrNull()?.let { 电影 ID ->//获取检索到的最后一项的远程键movieDatabase.remoteKeysDao().remoteKeysRepoId(movieId.movi​​eID)}}private suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState): RemoteKeys?{//分页库正在尝试在锚点位置之后加载数据//获取离锚点位置最近的item返回 state.anchorPosition?.let { position ->state.closestItemToPosition(position)?.movi​​eID?.let { movieId ->movieDatabase.remoteKeysDao().remoteKeysRepoId(movieId = movieId)}}}private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys?{//获取检索到的最后一页,其中包含项目.//从最后一页,获取最后一项返回 state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()?.let { 回购 ->//获取检索到的最后一项的远程键movieDatabase.remoteKeysDao().remoteKeysRepoId(movieId = repo.movi​​eID)}}}

  • 将 RemoteMediator 传递给分页数据

 val dataFlow : kotlinx.coroutines.flow.Flow>=寻呼机(getPagingConfig(),remoteMediator = MoviesMediator(authResponse,movieDatabase)){MoviePagingSource(authResponse)}.流.cachedIn(viewModelScope)

  • 在 MainActivity 中显示数据
<预><代码>@ExperimentalPagingApi私人乐趣 setUpAdapterOnline(){电影适配器 = 电影适配器()生命周期范围.launchWhenStarted {电影模型.dataFlow.collectLatest {movieAdapter.submitData(it)}}binding.recycler.adapter = 电影适配器binding.recycler.adapter = moviesAdapter.withLoadStateHeaderAndFooter(header = LoadingStateAdapter { 电影适配器.retry() },页脚 = LoadingStateAdapter { 电影适配器.retry() })}

解决方案

UPDATE

我尝试使用 this NewsApi 而不是 this Unsplash Api.即使 NewsApi 提供 per_page 作为查询参数,我仍然面临类似的问题,其中只有 first 2 页面加载,然后在调试它显示 Load 方法中的 APPEND 块被一遍又一遍地调用,分配给 page 的值总是 2.因此 下一页根本没有被获取.

原答案

@dlam 是对的,你需要遵循单一事实来源原则在这里,只从 MoviesDao 中的 Paging Source 请求数据.我没有找到您问题的确切解决方案,但我确实有一些发现要分享.

我会说我在示例代码实验室中调试了 Remote Mediator here,我发现它实际上在您滚动时增加了 page.但是,从您发送给我的 github 链接调试您的代码时,page 的值永远不会增加,并且 MoviesMediator 中的 APPEND 块被执行并且结束,即使不滚动.我试图找到原因,我唯一能想到的是你正在使用的 Api.我尝试了相同的功能,从 network 保存到 database 但使用 Unsplash Api 并且我也可以在离线时查看所有页面(与您的情况不同,不仅仅是一页).

我不确定代码实验室是否也将 per_page 参数作为 argument 传递到 Network/Api Service 接口内.就我而言,Unsplash Api 也允许将 per_page 作为参数传递.

在这里我附上一个链接到我的 github 存储库.尝试比较结果,如有任何疑问,请随时提出.

I have an application where i implemented paging library 3 to fetch data from api and paginate it , it works fine fetching data , the next implementation was to store the fetched data in room database , i have created the remotemediator class and wrote the code to store data , but the issue is that it stores only values of first page ( for example in my case im using the movie db api , each page fetched has 20 movies , and there are many pages ) , in my case it only saves the first 20 movies , even when i scroll , it is not storing more data , i have implemented the same exact code but seems to be the case , i faced it in an older project and now this one , i need some help , thank you in advance.

  • Movies Dao

 @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

@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 Class

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)
                }
    }

}

  • Passing RemoteMediator to paging data

    val dataFlow : kotlinx.coroutines.flow.Flow<PagingData<Result>> =
        Pager(getPagingConfig(),
        remoteMediator = MoviesMediator(authResponse,movieDatabase)){
            MoviePagingSource(authResponse)
        }.flow
            .cachedIn(viewModelScope)

  • Showing data in 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() }
        )
    }

解决方案

UPDATE

I tried using this NewsApi instead of this Unsplash Api. Even though the NewsApi provides the per_page as a query parameter, still I faced similar problem where only first 2 pages load and after that on debugging it shows that the APPEND block inside the Load method is getting called over and over and the value assigned to page is always 2. Hence the next pages aren't getting fetched at all.

ORIGINAL ANSWER

@dlam is right, you need to follow the Single source of truth principle here, and only request data from the Paging Source inside MoviesDao. I did not find the exact solution for your problem, but I do have some findings to share.

I would say that I debugged the Remote Mediator in the sample codelab here and I found that it actually increments the page as you scroll. However, when debugging your code from the github link you sent me, the value of page never increments and the APPEND block inside MoviesMediator is executed over and over, even when not scrolling. I tried to find the reason and the only thing I can come up with is the Api that you are using. I tried the same feature, saving from network to database but using the Unsplash Api and I was able to view all the pages when offline too (not just one page unlike your case).

I'm not sure if it is the per_page parameter that the codelab also passed as an argument inside the Network/Api Service Interface. In my case, the Unsplash Api also allowed to pass per_page as an argument.

Here I am attaching a link to my github repository. Try comparing the results and feel free to ask any more doubts.

这篇关于分页库仅在房间数据库中存储第一个获取的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆