Android Paging 3 如何过滤、排序和搜索我的数据

Android Paging 3 how to filter, sort and search my data

我正在尝试实现我正在使用 Room 的分页,我花了很长时间才意识到这一切都是为我完成的,但我需要做的是能够过滤搜索并对我的数据进行排序。我想暂时将它保留为 LiveData,以后我可以换成 flow。 我有这种过滤搜索和排序的方法,而且效果很好,

    @SuppressLint("DefaultLocale")
    private fun searchAndFilterPokemon(): LiveData<List<PokemonWithTypesAndSpecies>> {
        return Transformations.switchMap(search) { search ->
            val allPokemon = repository.searchPokemonWithTypesAndSpecies(search)
            Transformations.switchMap(filters) { filters ->
                val pokemon = when {
                    filters.isNullOrEmpty() -> allPokemon
                    else -> {
                        Transformations.switchMap(allPokemon) { pokemonList ->
                            val filteredList = pokemonList.filter { pokemon ->
                                pokemon.matches = 0
                                val filter = filterTypes(pokemon, filters)
                                filter
                            }
                            maybeSortList(filters, filteredList)
                        }
                    }
                }
                pokemon
            }
        }
    }

这里有几个开关映射,第一个是响应搜索更新

    var search: MutableLiveData<String> = getSearchState()

第二个正在响应过滤器更新

    val filters: MutableLiveData<MutableSet<String>> = getCurrentFiltersState()

第三个是观察搜索列表的更新,然后调用 filterTypes 和 maybeSortList 这些用于过滤和排序的小方法

    @SuppressLint("DefaultLocale")
    private fun filterTypes(
        pokemon: PokemonWithTypesAndSpecies,
        filters: MutableSet<String>
    ): Boolean {
        var match = false
        for (filter in filters) {
            for (type in pokemon.types) {
                if (type.name.toLowerCase() == filter.toLowerCase()) {
                    val matches = pokemon.matches.plus(1)
                    pokemon.apply {
                        this.matches = matches
                    }
                    match = true
                }
            }
        }
        return match
    }

    private fun maybeSortList(
        filters: MutableSet<String>,
        filteredList: List<PokemonWithTypesAndSpecies>
    ): MutableLiveData<List<PokemonWithTypesAndSpecies>> {
        return if (filters.size > 1)
            MutableLiveData(filteredList.sortedByDescending {
                Log.d("VM", "SORTING ${it.pokemon.name} ${it.matches}")
                it.matches
            })
        else MutableLiveData(filteredList)
    }

如前所述,我想将它们迁移到分页 3,但我很难做到我已将我的存储库和 dao 更改为 return a PagingSource,我只想将我的视图模型更改为 return PagingData 作为实时数据,到目前为止我有这个

@SuppressLint("DefaultLocale")
private fun searchAndFilterPokemonPager(): LiveData<PagingData<PokemonWithTypesAndSpecies>> {
    val pager =  Pager(
        config = PagingConfig(
            pageSize = 50,
            enablePlaceholders = false,
            maxSize = 200
        )
    ){
        searchAndFilterPokemonWithPaging()
    }.liveData.cachedIn(viewModelScope)

    Transformations.switchMap(filters){
        MutableLiveData<String>()
    }

    return Transformations.switchMap(search) {search ->
        val searchedPokemon =
            MutableLiveData<PagingData<PokemonWithTypesAndSpecies>>(pager.value?.filter { it.pokemon.name.contains(search) })
        Transformations.switchMap(filters) { filters ->
            val pokemon = when {
                filters.isNullOrEmpty() -> searchedPokemon
                else -> {
                    Transformations.switchMap(searchedPokemon) { pokemonList ->
                        val filteredList = pokemonList.filter { pokemon ->
                            pokemon.matches = 0
                            val filter = filterTypes(pokemon, filters)
                            filter
                        }
                        maybeSortList(filters, filteredList = filteredList)
                    }
                }
            }
            pokemon
        }
    }
}

但是 switchmap 给我一个错误

Type inference failed: Cannot infer type parameter Y in 

fun <X : Any!, Y : Any!> switchMap
(
    source: LiveData<X!>,
    switchMapFunction: (input: X!) → LiveData<Y!>!
 )

我想我明白了,但不确定如何修复它,过滤器和排序方法也不再起作用,我看不到任何好的方法可以用 PageData 替代它,它有过滤器但没有一种?任何帮助表示赞赏 : LiveData

更新感谢@Shadow,我重写了它以使用中介实时数据实现搜索,但我仍然停留在过滤

    init {
        val combinedValues =
            MediatorLiveData<Pair<String?, MutableSet<String>?>?>().apply {
                addSource(search) {
                    value = Pair(it, filters.value)
                }
                addSource(filters) {
                    value = Pair(search.value, it)
                }
            }

        searchPokemon = Transformations.switchMap(combinedValues) { pair ->
            val search = pair?.first
            val filters = pair?.second
            if (search != null && filters != null) {
                searchAndFilterPokemonPager(search)
            } else null
        }
    }



    @SuppressLint("DefaultLocale")
    private fun searchAndFilterPokemonPager(search: String): LiveData<PagingData<PokemonWithTypesAndSpecies>> {
        return Pager(
            config = PagingConfig(
                pageSize = 50,
                enablePlaceholders = false,
                maxSize = 200
            )
        ){
            searchAllPokemonWithPaging(search)
        }.liveData.cachedIn(viewModelScope)

    }

    @SuppressLint("DefaultLocale")
    private fun searchAllPokemonWithPaging(search: String): PagingSource<Int, PokemonWithTypesAndSpecies> {
        return repository.searchPokemonWithTypesAndSpeciesWithPaging(search)
    }

我不相信你在使用寻呼源时可以在接收端正确排序。这是因为分页将 return 来自数据库的数据块,无论其顺序如何,或者您直接在其查询中指定的顺序。

假设您在数据库中有一个姓名列表,并且您希望按字母顺序显示它们。

除非您实际在查询本身中指定排序,否则默认查询将提取 X 个名字(X 是您配置的分页大小),它以数据库用于该查询结果的顺序查找.

您可以对结果进行排序,但它只会对那些 X 名字进行排序 returned。这意味着,如果您有任何以 A 开头的名称,而它们碰巧不在该名称块中,则它们只会在您滚动到足以让寻呼机加载它们时显示,然后它们才会显示将在当前列表中正确显示排序。每当将新页面加载到列表中时,您可能会看到名称因此而四处移动。

这是排序,现在是搜索。

我最终为我的搜索所做的只是放弃了数据库自身的搜索能力。您可以直接在查询中使用“LIKE”,但除非您的搜索结构非常基本,否则它将毫无用处。还有 Fts4 可用:https://developer.android.com/reference/androidx/room/Fts4

但设置和使用 PIA 如此之多,以至于我最终没有看到任何值得为我的案例付出努力的回报。

所以我只是进行搜索,但我希望在接收端使用 Transformations.switchMap 来触发从数据库中提取新数据,只要用户输入发生变化,同时对我接收到的数据进行过滤。

您已经实现了其中的一部分,只需取出 Transformations.switchMap(filters) 的内容并简单地 return 数据,然后对 return 输入的结果进行搜索附加到 searchAndFilterPokemonPager 调用的观察者。

过滤器与搜索的逻辑相同,但我建议确保在搜索之前先进行过滤,因为通常搜索是输入驱动的,如果您不添加去抖器,它将触发对每个新搜索用户输入或删除的字符。

简而言之:

  1. 直接在查询中排序,这样您收到的结果已经排序
  2. 实现附加到过滤器值的 switchMap 以触发新的数据获取,并考虑新的过滤器值
  3. 像过滤器一样实现 switchMap,但用于搜索输入
  4. 在提交给 list/recyclerview 适配器之前过滤 returned 数据
  5. 同上,但用于搜索

所以这至少部分可能使用流程,但排序不可能见这里 https://issuetracker.google.com/issues/175430431,我还没有想出排序但搜索和过滤是可能的,所以对于过滤我有非常相同的query 是一个 LIKE 查询,由一些实时数据(搜索值)触发,这里是搜索方法

    @SuppressLint("DefaultLocale")
    private fun searchPokemonPager(search: String): LiveData<PagingData<PokemonWithTypesAndSpeciesForList>> {
        return Pager(
            config = PagingConfig(
                pageSize = 50,
                enablePlaceholders = false,
                maxSize = 200
            )
        ) {
            searchAllPokemonWithPaging(search)
        }.liveData.cachedIn(viewModelScope)
    }


    @SuppressLint("DefaultLocale")
    private fun searchAllPokemonWithPaging(search: String): PagingSource<Int, PokemonWithTypesAndSpeciesForList> {
        return repository.searchPokemonWithTypesAndSpeciesWithPaging(search)
    }

repo 正在调用 dao,现在要执行过滤 @Shadow 建议使用寻呼机,这可行,但使用 combine 的流更容易,我的过滤器是实时数据,流有一些方便的扩展,如 asFlow 所以它变得像

一样简单
    @SuppressLint("DefaultLocale")
    private fun searchAndFilterPokemonPager(search: String): Flow<PagingData<PokemonWithTypesAndSpeciesForList>> {
        val pager = Pager(
            config = PagingConfig(
                pageSize = 50,
                enablePlaceholders = false,
                maxSize = 200
            )
        ) {
            searchAllPokemonWithPaging(search)
        }.flow.cachedIn(viewModelScope).combine(filters.asFlow()){ pagingData, filters ->
            pagingData.filter { filterTypesForFlow(it, filters) }
        }
        return pager
    }

很明显,我在这里更改了 return 类型,因此我们还必须修复我们的中介实时数据,因为它需要实时数据来发出我们的搜索和过滤器,稍后我会修复这个所以它都使用流

    init {
        val combinedValues =
            MediatorLiveData<Pair<String?, MutableSet<String>?>?>().apply {
                addSource(search) {
                    value = Pair(it, filters.value)
                }
                addSource(filters) {
                    value = Pair(search.value, it)
                }
            }

        searchPokemon = Transformations.switchMap(combinedValues) { pair ->
            val search = pair?.first
            val filters = pair?.second
            if (search != null && filters != null) {
                searchAndFilterPokemonPager(search).asLiveData()
            } else null
        }
    }

这里我们只使用 asLiveData 扩展