#android #android-room #android-jetpack #android-paging #android-paging-library
Вопрос:
Я борюсь с библиотекой подкачки 3 Jetpack.
Я настроил
- Доработка для вызовов сетевого API
- Место для хранения полученных данных
- Хранилище, в котором отображается пейджер.поток (см. код ниже)
- Удаленный посредник для кэширования сетевых результатов в базе данных комнат
То PagingSource
создается комнатой.
Я понимаю, что RemoteMediator
ответственность s заключается в том, чтобы извлекать элементы из сети и сохранять их в базе данных комнат. Поступая таким образом, мы можем использовать базу данных комнат как единую точку истины. Комната может легко создать PagingSource
для меня, пока я использую целые числа как nextPageKeys
.
Пока все хорошо. Вот моя модель просмотра для получения списка 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
)
Вот хранилище для вызова подкачки
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 не будет воссоздан, ни источник страниц. Все запросы по-прежнему пытаются получить исходные записи.
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)
}
}
В пользовательском интерфейсе пользователь сначала выбирает проект, который поступает из ProjectViewModel
via LiveData. Как только у нас появится информация о проекте, мы установим ее с SourcesViewModel
помощью setProject
метода, описанного выше.
Фрагмент:
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)
})
}
Ответ №1:
Общий вывод подкачки-это a Flow<PagingData>
, поэтому обычно смешивание вашего сигнала (пути к файлу) в поток с помощью некоторой операции потока будет работать лучше всего. Если вы можете смоделировать путь , по которому пользователь нажимает как a Flow<String>
, что-то подобное может сработать:
ViewModel.kt
class MyViewModel extends .. {
val pathFlow = MutableStateFlow<String>("/")
val pagingDataFlow = pathFlow.flatMapLatest { path ->
Pager(
remoteMediator = MyRemoteMediator(path)
...
).flow.cachedIn(..)
}
}
Удаленный посредник.kt
class MyRemoteMediator extends RemoteMediator<..> {
override suspend fun load(..): .. {
// If path changed or simply on whenever loadType == REFRESH, clear db.
}
}
Другая стратегия , если у вас все загружено, — это передать путь напрямую PagingSource
, но похоже, что ваши данные поступают из сети, поэтому RemoteMediator
здесь, вероятно, лучше всего подходить.
Комментарии:
1. Большое вам спасибо за этот вклад! Теперь я продвинулся на шаг дальше. Но у меня есть два последующих вопроса, потому что это еще не работает: 1. Компилятор разрешает использовать только
flatMapConcat
вместоflatMap
. Имеет ли это существенное значение? 2. Если я вызываю в своей моделиpathFlow.emit("/new/path")
представления, тоpagingDataFlow
она не обновляется, хотя я собираю ее в своем фрагменте. У вас есть идеи, почему? Я обновлю свой вопрос, чтобы дать вам свой текущий код.2. Ах, извините, я забыл, что
Flow.flatMap
сейчас это устарело,flatMapConcat
это правильный вариант, какflatMapMerge
и для ограничения concurrenct > 1.3. Для второго вопроса — вы собираете данные с помощью
collect
pagingDataFlow илиcollectLatest
? Обязательно используйтеcollectLatest
, так как оно отменит предыдущее поколение, как только появится новое, так.submitData()
как может работать в течение некоторого времени, прежде чем аннулирование закроет его.4. Я попробовал и
collect
то иcollectLatest
другое . Ничего не сработало. Я обновил код в своем вопросе. Высоко ценю ваши комментарии!5. С этим решением есть проблема: если действие будет воссоздано или если пользователь покинет приложение и вернется к нему, коллекция потоков будет перезапущена. MutableStateFlow затем повторно выдаст свое последнее значение, в результате чего будет создан новый экземпляр пейджера. Таким образом, кэширование данных подкачки бесполезно, потому что все будет загружаться с нуля в каждой новой коллекции потоков.