Android 分页 3:如何更改 RemoteMediator 的参数 [英] Android Paging 3: How to change parameters of RemoteMediator

查看:28
本文介绍了Android 分页 3:如何更改 RemoteMediator 的参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为 Jetpack 的 Paging 3 库苦苦挣扎.

I am struggling with the Paging 3 Library of Jetpack.

我设置

  • 针对网络 API 调用进行改造
  • 存储检索数据的空间
  • 公开 Pager.flow 的存储库(请参阅下面的代码)
  • 在房间数据库中缓存网络结果的 RemoteMediator

PagingSource 由 Room 创建.

The PagingSource is created by Room.

我知道 RemoteMediator 的职责是从网络中获取项目并将它们保存到 Room 数据库中.通过这样做,我们可以使用 Room 数据库作为单一事实点.只要我使用整数作为 nextPageKeys,Room 就可以轻松地为我创建 PagingSource.

I understand that the RemoteMediators responsibility is to fetch items from the network and persist them into the Room database. By doing so, we can use the Room database as single point of truth. Room can easily create the PagingSource for me as long as I am using Integers as nextPageKeys.

到目前为止一切顺利.这是我的 ViewModel,用于检索 Sources 列表:

So far so good. Here is my ViewModel to retrieve a list of Sources:

    private lateinit var _sources: Flow<PagingData<Source>>
    val sources: Flow<PagingData<Source>>
        get() = _sources

    
    private fun fetchSources() = viewModelScope.launch {
        _sources = sourcesRepository.getSources(
            selectedRepositoryUuid,
            selectedRef,
            selectedPath
        )
    }

val sources 收集在 Fragment 中.

fetchSources() 在三个参数之一发生变化时被调用(selectedRepositoryUuidselectedRefselectedPath)

fetchSources() is called whenever one of the three parameters change (selectedRepositoryUuid, selectedRef or selectedPath)

这是 Paging 调用的存储库

Here is the Repository for the Paging call

    fun getSources(repositoryUuid: String, refHash: String, path: String): Flow<PagingData<Source>> {
        return Pager(
            config = PagingConfig(50),
            remoteMediator = SourcesRemoteMediator(repositoryUuid, refHash, path),
            pagingSourceFactory = { sourcesDao.get(repositoryUuid, refHash, path) }
        ).flow
    }

现在我的经验是 Repository.getSources 首先用正确的参数调用,RemoteMediatorPagingSource 被创建,一切都是好的.但是,一旦 3 个参数之一发生变化(比如 path),既不会重新创建 RemoteMediator,也不会重新创建 PagingSource.所有请求仍会尝试获取原始条目.

Now what I experience is that Repository.getSources is first called with correct parameters, the RemoteMediator and the PagingSource are created and all is good. But as soon as one of the 3 parameters change (let's say path), neither the RemoteMediator is recreated nor the PagingSource. All requests still try to fetch the original entries.

如果它有助于理解我的用例:RecyclerView 显示文件和文件夹的分页列表.一旦用户点击一个文件夹,RecyclerView 的内容应该会改变以显示点击文件夹的文件.

If it helps to grasp my use-case: The RecyclerView is displaying a paged list of files and folders. As soon as the user clicks on a folder, the content of the RecyclerView should change to display the files of the clicked folder.

更新:

感谢 dlam 的回答,代码现在是这样的.代码是对真实代码的简化.我基本上将所有需要的信息都封装在 SourceDescription 类中.:

Thanks to the answer of dlam, the code now looks like this. The code is a simplification of the real code. I basically encapsulate all needed information in the SourceDescription class.:

视图模型:

    private val sourceDescription = MutableStateFlow(SourceDescription())

    fun getSources() = sourceDescription.flatMapConcat { sourceDescription ->

        // This is called only once. I expected this to be called whenever `sourceDescription` emits a new value...?

        val project = sourceDescription.project
        val path = sourceDescription.path

        Pager(
            config = PagingConfig(30),
            remoteMediator = SourcesRemoteMediator(project, path),
            pagingSourceFactory = { sourcesDao.get(project, path) }
        ).flow.cachedIn(viewModelScope)
    }

    fun setProject(project: String) {
        viewModelScope.launch {
            val defaultPath = Database.getDefaultPath(project)
            val newSourceDescription = SourceDescription(project, defaultPath)
            sourceDescription.emit(newSourceDescription)
        }
    }

在 UI 中,用户首先选择一个项目,该项目通过 LiveData 来自 ProjectViewModel.一旦我们有了项目信息,我们就使用上面的 setProject 方法在 SourcesViewModel 中设置它.

In the UI, the User first selects a project, which is coming from the ProjectViewModel via LiveData. As soon as we have the project information, we set it in the SourcesViewModel using the setProject method from above.

片段:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // load the list of sources
        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            sourcesViewModel.getSources().collectLatest { list ->
                sourcesAdapter.submitData(list) // this is called only once in the beginning
            }
        }

        projectsViewModel.projects.observe(viewLifecycleOwner, Observer { project -> 
            sourcesViewModel.setProject(project)
        })
    }

推荐答案

Paging 的整体输出是一个 Flow,因此通常将您的信号(文件路径)通过一些流操作将工作得最好.如果您能够将用户单击的路径建模为 Flow,则类似这样的操作可能会奏效:

The overall output of Paging is a Flow<PagingData>, so typically mixing your signal (file path) into the flow via some flow-operation will work best. If you're able to model the path the user clicks on as a Flow<String>, something like this might work:

ViewModel.kt

ViewModel.kt

class MyViewModel extends .. {
  val pathFlow = MutableStateFlow<String>("/")
  val pagingDataFlow = pathFlow.flatMapLatest { path ->
    Pager(
      remoteMediator = MyRemoteMediator(path)
      ...
    ).flow.cachedIn(..)
  }
}

RemoteMediator.kt

RemoteMediator.kt

class MyRemoteMediator extends RemoteMediator<..> {
  override suspend fun load(..): .. {
    // If path changed or simply on whenever loadType == REFRESH, clear db.
  }
}

如果您加载了所有内容,另一种策略是将路径直接传递到 PagingSource,但听起来您的数据来自网络,因此 RemoteMediator 方法可能是最好的在这里.

The other strategy if you have everything loaded is to pass the path directly into PagingSource, but it sounds like your data is coming from network so RemoteMediator approach is probably best here.

这篇关于Android 分页 3:如何更改 RemoteMediator 的参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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