【问题标题】:Android Paging 3 how to filter, sort and search my dataAndroid Paging 3 如何过滤、排序和搜索我的数据
【发布时间】:2020-12-06 20:19:30
【问题描述】:

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

    @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
            }
        }
    }

这里有几个switchmap,第一个是响应搜索更新

    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 以返回 PagingSource,我只想更改我的视图模型以将 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 替换它的好方法,它有一个过滤器但没有排序?任何帮助表示赞赏 : 实时数据

更新感谢@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)
    }

【问题讨论】:

    标签: android sorting android-paging android-paging-3


    【解决方案1】:

    我不相信您在使用寻呼源时可以在接收端正确排序。这是因为分页会以任何顺序从数据库中返回一大块数据,或者以您在查询中直接指定的任何顺序。

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

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

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

    这是排序,现在是搜索。

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

    但设置和使用的 PIA 就是这样一个 PIA,我最终看到完全没有值得为我的案例付出努力的回报。

    所以我只是进行搜索,但是我希望在接收端使用Transformations.switchMap 来触发从数据库中获取新数据,只要用户输入发生更改,再加上我收到的数据的过滤。

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

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

    简而言之:

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

    【讨论】:

    • 感谢您的回答,难道这不能使用分页源来实现吗?
    • 老实说,我不知道,从来没有这样想过,但我认为在接收端工作的任何东西也可以在寻呼源中工作。但是,为此我会让其他更有经验的人提供他们的意见,因为在使用 Room 集成时我几乎不需要制作自己的分页源。
    • 但是您还必须将类型关系表拉到查询中,然后才能正确执行该过滤器。 select * from pokemon left join pokemonTypes on pokemon.id = pokemonTypes.id AND pokemonTypes.type = :filter1 AND pokemonTypes.type =: filter2 AND ... 之类的东西与用户选择的“ands”一样多,您在 rawquery 中生成这些 ands。结果将是每个口袋妖怪的列表,这些口袋妖怪至少制作了一个或多个过滤器,如果你想对它们进行分组和排序,你可以在所有“和”之后这样做
    • 不是真的,分页是要走的路,当您决定使用分页时,您走在正确的轨道上,因为它只按需加载部分数据,而不是直接将整个列表拉入内存。
    • 添加了一个仍然不确定排序的答案
    【解决方案2】:

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

        @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 使用 flow 更容易,我的过滤器是实时数据,flow 有一些方便的扩展,如 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
        }
    

    很明显,我在这里更改了返回类型,所以我们还必须修复我们的中介实时数据,因为它期望实时数据发出我们的搜索和过滤器,后来我修复了这个问题,所以它都使用流

        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 扩展

    【讨论】:

    • 排序会比较棘手,因为如果我理解正确,你想按类型排序,但每个结果可以有不止一种类型,所以你必须想出你想如何解释排序多种类型,因为示例:先按匹配最多的类型进行排序,然后按字母顺序或仅按字母顺序排序,而不管匹配类型的数量或您认为最好的其他顺序
    • yh 我希望它按匹配类型和 ID 排序我在这里提出了一个问题 issuetracker.google.com/issues/175430431 我们会看看会发生什么,再次感谢
    • 只是一个建议,在您的问题中包含一些数据示例,以便更容易理解您想要实现的目标,而不是仅仅描述它,这会很有帮助。跨度>
    • @Shadow 不需要他们已经说过他们不会解决这个问题
    • 基本又回到了我之前说的;排序必须在分页接收数据之前完成,这是有道理的。分页旨在“将这个巨大的列表分成大小为 Y 的 X 块”,这就是它所能做的。
    猜你喜欢
    • 1970-01-01
    • 2020-01-18
    • 2019-01-04
    • 2018-11-10
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    相关资源
    最近更新 更多