Android Room Paging3 правильный подход к динамической фильтрации

#android #android-room #android-paging-3

#Android #android-room #android-paging-3

Вопрос:

Я изучаю новую библиотеку подкачки Android Room

    implementation "androidx.paging:paging-runtime-ktx:3.0.0-alpha09"
 

В моей исходной базе данных содержится около 10 000 строк, и я фильтрую по первому символу поля name следующим образом:-

DAO

 @Query("SELECT * from citation_style WHERE citation_style_name LIKE :startsWith ORDER BY citation_style_name ASC")
fun fetch(startsWith: String): PagingSource<Int, CitationStyleDO>
 

Репозиторий

 fun fetch(startsWith: String): Flow<PagingData<CitationStyleDO>> {
    return Pager(
        PagingConfig(pageSize = 60, prefetchDistance = 30, enablePlaceholders = false, maxSize = 200)
    ) { database.citationStyleDao().fetch("$startsWith%") }.flow
}
 

ViewModel

 fun fetch(startsWith: String): Flow<PagingData<CitationStyleDO>> {
    return repository.fetch(startsWith).cachedIn(viewModelScope)
}
 

Фрагмент

 override fun onStartsWithClicked(startsWith: String) {
    lifecycleScope.launch {
        viewModel.fetch(startsWith).collectLatest { adapter.submitData(it) }
    }
}
 

Является ли это правильным подходом при повторном использовании lifecycleScope.launch {...} каждый раз, когда изменяется символ Starts With?

Должен ли я быть map{} или switchMap{} запускаться MutabaleLiveData<String> for StartwWith?

Ответ №1:

Это не сработает, потому что submitData не возвращается до PagingData тех пор, пока не будет признана недействительной. Вы можете столкнуться со сценариями гонки , когда у вас запущено несколько заданий , где PagingDataAdapter выполняется попытка сбора данных из нескольких PagingData .

Более «потоковым» способом было бы превратить ваши вызовы выборки в поток и объединить их с вашими Flow<PagingData> , что будет автоматически распространять отмену при каждом изменении вашего запроса.

Пара других вещей:

Рекомендуется позволить подкачке выполнять фильтрацию за вас, так как таким образом вы можете избежать повторной выборки из базы данных каждый раз, когда изменяется ваш поиск, а также вы можете использовать подкачку для обработки изменений конфигурации и восстановления состояния.

Вы должны использовать viewLifecycleOwner вместо lifecycleScope прямого, потому что вы не хотите, чтобы подкачка выполняла работу после уничтожения представления фрагмента

например,

ViewModel

 val queryFlow = MutableStateFlow("init_query")
val pagingDataFlow = Pager(...) {
        dao.pagingSource()
    }.flow
    // This multicasts, to prevent combine from refetching
    .cachedIn(viewModelScope)
    .combine(queryFlow) { pagingData, query -> 
        pagingData.filter { it.startsWith(query)
    }
    // Optionally call .cachedIn() here a second time to cache the filtered results. 
 

Фрагмент

 override fun onStartsWithClicked(startsWith: String) {
    viewModel.queryFlow = startsWith
}

override fun onViewCreated(...) {
     viewLifecycleOwner.lifecycleScope.launch {
viewModel.pagingDataFlow.collectLatest { adapter.submitData(it) }
}
 

Примечание: вы определенно можете использовать Room для выполнения фильтрации, если хотите, вероятно, правильный способ заключается .flatMapLatest в том, чтобы выполнить поток запросов и вернуть новый Pager каждый зубец и передать термин запроса в функцию dao, которая возвращает PagingSource

ViewModel

 queryFlow.flatMapLatest { query ->
    Pager(...) { dao.pagingSource(query) }
        .cachedIn(...)
}
 

Комментарии:

1. Как насчет фильтрации, а также выполнения вызова api? Я предполагаю, что вы можете выполнить вызов api, который сохраняет ответ в БД, который все равно позже отправляется в PagingSource

2. Что вы подразумеваете под вызовом API здесь? Вы имеете в виду удаленную выборку для большего количества элементов? Это происходит на отдельных слоях при подкачке, поэтому это не меняет того, как вы будете применять оператор фильтра в примере в моем ответе.

3. Проблема с объединением здесь заключается в том, что страницы, которые были удалены, не будут отфильтрованы правильно? итак, единственный способ отфильтровать все — позволить room выполнять фильтрацию

4. @DennisVA если страница удалена, она не будет видна в пользовательском интерфейсе, если она будет повторно вставлена, преобразование фильтра будет выполнено снова. Преобразования PagingData являются инкрементными, поэтому вам никогда не придется беспокоиться о том, что они «пропускают» страницу.