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

mar*_*987 10 sorting android android-paging android-paging-3

我正在尝试实现我正在使用 Room 的分页,我花了很长时间才意识到这一切都为我完成了,但我需要做的是能够过滤搜索并对我的数据进行排序。我想暂时将其保留为 LiveData,以便稍后交换为 Flow。\n我使用此方法来过滤搜索和排序,并且效果非常好,

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

这里有一些切换映射,第一个是响应搜索更新

\n
    var search: MutableLiveData<String> = getSearchState()\n
Run Code Online (Sandbox Code Playgroud)\n

第二个是响应过滤器更新

\n
    val filters: MutableLiveData<MutableSet<String>> = getCurrentFiltersState()\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
    @SuppressLint("DefaultLocale")\n    private fun filterTypes(\n        pokemon: PokemonWithTypesAndSpecies,\n        filters: MutableSet<String>\n    ): Boolean {\n        var match = false\n        for (filter in filters) {\n            for (type in pokemon.types) {\n                if (type.name.toLowerCase() == filter.toLowerCase()) {\n                    val matches = pokemon.matches.plus(1)\n                    pokemon.apply {\n                        this.matches = matches\n                    }\n                    match = true\n                }\n            }\n        }\n        return match\n    }\n\n    private fun maybeSortList(\n        filters: MutableSet<String>,\n        filteredList: List<PokemonWithTypesAndSpecies>\n    ): MutableLiveData<List<PokemonWithTypesAndSpecies>> {\n        return if (filters.size > 1)\n            MutableLiveData(filteredList.sortedByDescending {\n                Log.d("VM", "SORTING ${it.pokemon.name} ${it.matches}")\n                it.matches\n            })\n        else MutableLiveData(filteredList)\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

如前所述,我想将这些迁移到 paging 3,但很难做到这一点我已经更改了我的存储库和 dao 以返回 PagingSource,我只想更改我的视图模型以将 PagingData 作为实时数据返回,到目前为止我有这个

\n
@SuppressLint("DefaultLocale")\nprivate fun searchAndFilterPokemonPager(): LiveData<PagingData<PokemonWithTypesAndSpecies>> {\n    val pager =  Pager(\n        config = PagingConfig(\n            pageSize = 50,\n            enablePlaceholders = false,\n            maxSize = 200\n        )\n    ){\n        searchAndFilterPokemonWithPaging()\n    }.liveData.cachedIn(viewModelScope)\n\n    Transformations.switchMap(filters){\n        MutableLiveData<String>()\n    }\n\n    return Transformations.switchMap(search) {search ->\n        val searchedPokemon =\n            MutableLiveData<PagingData<PokemonWithTypesAndSpecies>>(pager.value?.filter { it.pokemon.name.contains(search) })\n        Transformations.switchMap(filters) { filters ->\n            val pokemon = when {\n                filters.isNullOrEmpty() -> searchedPokemon\n                else -> {\n                    Transformations.switchMap(searchedPokemon) { pokemonList ->\n                        val filteredList = pokemonList.filter { pokemon ->\n                            pokemon.matches = 0\n                            val filter = filterTypes(pokemon, filters)\n                            filter\n                        }\n                        maybeSortList(filters, filteredList = filteredList)\n                    }\n                }\n            }\n            pokemon\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但 switchmap 给了我一个错误

\n
Type inference failed: Cannot infer type parameter Y in \n\nfun <X : Any!, Y : Any!> switchMap\n(\n    source: LiveData<X!>,\n    switchMapFunction: (input: X!) \xe2\x86\x92 LiveData<Y!>!\n )\n
Run Code Online (Sandbox Code Playgroud)\n

我想我明白,但不知道如何修复它,过滤器和排序方法也将不再工作,我看不到任何好的方法用 PageData 来替代它,它有一个过滤器但没有排序?感谢任何帮助\n:LiveData<Y!

\n

感谢@Shadow的更新,我已经重写了它以使用中介实时数据来实现搜索,但我仍然坚持过滤

\n
    init {\n        val combinedValues =\n            MediatorLiveData<Pair<String?, MutableSet<String>?>?>().apply {\n                addSource(search) {\n                    value = Pair(it, filters.value)\n                }\n                addSource(filters) {\n                    value = Pair(search.value, it)\n                }\n            }\n\n        searchPokemon = Transformations.switchMap(combinedValues) { pair ->\n            val search = pair?.first\n            val filters = pair?.second\n            if (search != null && filters != null) {\n                searchAndFilterPokemonPager(search)\n            } else null\n        }\n    }\n\n\n\n    @SuppressLint("DefaultLocale")\n    private fun searchAndFilterPokemonPager(search: String): LiveData<PagingData<PokemonWithTypesAndSpecies>> {\n        return Pager(\n            config = PagingConfig(\n                pageSize = 50,\n                enablePlaceholders = false,\n                maxSize = 200\n            )\n        ){\n            searchAllPokemonWithPaging(search)\n        }.liveData.cachedIn(viewModelScope)\n\n    }\n\n    @SuppressLint("DefaultLocale")\n    private fun searchAllPokemonWithPaging(search: String): PagingSource<Int, PokemonWithTypesAndSpecies> {\n        return repository.searchPokemonWithTypesAndSpeciesWithPaging(search)\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

Sha*_*dow 9

我不相信当您使用分页源时您可以在接收端正确排序。这是因为分页将从数据库返回一块数据,无论其顺序如何,或者按照您在查询中直接指定的顺序。

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

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

您可以对结果进行排序,但它只会对返回的 X 个名字进行排序。这意味着,如果您有任何以 开头的名称,A并且它们碰巧没有出现在该名称块中,则只有当您滚动到足够远以供寻呼机加载它们时,它们才会显示,只有那时它们才会显示正确排序目前的清单。每当新页面加载到列表中时,您可能会看到名称四处移动。

这是用于排序,现在是用于搜索。

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

但它的设置和使用是如此的 PIA,以至于我最终发现绝对没有任何回报值得为我的案例付出努力。

因此,我只是在接收端进行我想要的搜索,而不是使用 aTransformations.switchMap每当用户输入发生变化以及对我收到的数据进行过滤时触发从数据库中获取新的数据。

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

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

简而言之:

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

  • 添加了一个仍然不确定排序的答案 (2认同)