I am struggling with the Paging 3 Library of Jetpack.
I setup
- Retrofit for the network API calls
- Room to store the retrieved data
- A repository that exposes the Pager.flow (see code below)
- A RemoteMediator to cache the network results in the room database
The PagingSource
is created by Room.
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
.
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
is collected in the Fragment
.
fetchSources()
is called whenever one of the three parameters change (selectedRepositoryUuid
, selectedRef
or selectedPath
)
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
}
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.
My question: How can I use the Paging 3 library here in cases where the paging content is dependent on dynamic variables?
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.
Update:
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.:
ViewModel:
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)
}
}
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.
Fragment:
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)
})
}