Android Подкачка 3: Как изменить параметры RemoteMediator

#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 затем повторно выдаст свое последнее значение, в результате чего будет создан новый экземпляр пейджера. Таким образом, кэширование данных подкачки бесполезно, потому что все будет загружаться с нуля в каждой новой коллекции потоков.