6

我正在尝试实现我正在使用 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
            }
        }
    }

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

    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 替换它的好方法,它有一个过滤器但没有排序?任何帮助表示赞赏:LiveData<Y!

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

2 回答 2

5

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

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

除非您实际上在查询本身中指定排序,否则默认查询将获取 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. 同上,但用于搜索
于 2020-12-10T10:16:59.250 回答
0

所以这至少可以部分使用流,但排序是不可能的,请参见这里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 扩展

于 2020-12-12T21:26:35.137 回答