Android 分页 3:如何更改 RemoteMediator 的参数 [英] Android Paging 3: How to change parameters of 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 RemoteMediator
s 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()
在三个参数之一发生变化时被调用(selectedRepositoryUuid
、selectedRef
或 selectedPath
)
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
首先用正确的参数调用,RemoteMediator
和 PagingSource
被创建,一切都是好的.但是,一旦 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屋!