首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何使用MutableStateFlow搜索列表中的项

如何使用MutableStateFlow搜索列表中的项
EN

Stack Overflow用户
提问于 2022-06-11 22:37:14
回答 1查看 990关注 0票数 0

我在学机器人的kotlin流。我想基本上即时搜索在我的列表和过滤器,以显示在再浏览。我在谷歌搜索,发现了这个惊人的5~6成熟帖子。这篇文章基本上是从谷歌搜索的。我想要在列表中搜索项目,并在回圈视图中显示。有人能指点我怎么开始这个吗。我正在做更详细的计划

假设我有一个SearchBox和一个Reyclerview,其中一个项目abc 1,abc 2,xyz 1,xyz 2.等.

当所有数据合并时,主图像

场景1

当我开始输入SearchBox并输入small 大写A时,我只想在回收视图中显示两个匹配项,如下所示

场景2

当我在SearchBox中输入任何错误的文本时,我想基本上显示一个找不到的文本消息,如下所示

任何指导都是很好的。谢谢

我正在添加我的代码

ExploreViewModel.kt

代码语言:javascript
运行
复制
class ExploreViewModel(private var list: ArrayList<Category>) : BaseViewModel() {

    val filteredTopics = MutableStateFlow<List<opics>>(emptyList())
    var topicSelected: TopicsArea? = TopicsArea.ALL
        set(value) {
            field = value
            handleTopicSelection(field ?: TopicsArea.ALL)
        }


    private fun handleTopicSelection(value: TopicsArea) {
        if (value == TopicsArea.ALL) {
            filterAllCategories(true)
        } else {
            filteredTopics.value = list.firstOrNull { it.topics != null && it.title == value.title }
                ?.topics?.sortedBy { topic -> topic.title }.orEmpty()
        }
    }

    fun filterAllCategories(isAllCategory: Boolean) {
        if (isAllCategory && topicSelected == TopicsArea.ALL && !isFirstItemIsAllCategory()) {
            list.add(0, code = TopicsArea.ALL.categoryCode))
        } else if (isFirstItemIsAllCategory()) {
            list.removeAt(0)
        }
        filteredTopics.value = list.flatMap { it.topics!! }.distinctBy { topic -> topic.title }.sortedBy { topic -> topic.title }
    }

    private fun isFirstItemIsAllCategory() = list.firstOrNull()?.code == TopicsArea.ALL
}

xml

代码语言:javascript
运行
复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.SearchView
        android:id="@+id/searchView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginEnd="16dp"
        app:closeIcon="@drawable/ic_cancel"
        app:layout_constraintBottom_toTopOf="@+id/exploreScroll"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintVertical_chainStyle="packed" />

    <HorizontalScrollView
        android:id="@+id/exploreScroll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:scrollbars="none"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/searchView">

        <com.google.android.material.chip.ChipGroup
            android:id="@+id/exploreChips"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:chipSpacingHorizontal="10dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:singleLine="true"
            app:singleSelection="true" />

    </HorizontalScrollView>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/exploreList"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="20dp"
        android:paddingTop="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_default="wrap"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/exploreScroll" />

</androidx.constraintlayout.widget.ConstraintLayout>

Category.kt

代码语言:javascript
运行
复制
@Parcelize
data class Category(
    val id: String? = null,
    val title: String? = null,
    val code: String? = null,
    val topics: List<Topics>? = null,
) : Parcelable

Topics.kt

代码语言:javascript
运行
复制
@Parcelize
data class Topics(
    val id: String? = null,
    val title: String? = null
) : Parcelable

虚拟数据和来自服务器

代码语言:javascript
运行
复制
fun categoriesList() = listOf(
    Categories("21", "physical", listOf(Topics("1", "Abc one"), Topics("2", "Abc Two"))),
    Categories("2211", "mind", listOf(Topics("1", "xyz one"), Topics("2", "xyz two"))),
    Categories("22131", "motorized", listOf(Topics("1", "xyz three"), Topics("2", "xyz four"))),
)

在我看来,模型list保存在虚拟数据之上。在我的回收视图中,我正在传递整个对象,我正在做flatMap,将所有数据合并到列表中。确保在回收视图中使用Topictitle属性。在图像Abc one中,Abc twoTopic中占有一席之地。谢谢

在@Tenfour04建议之后,我将转到A2建议,因为我已经有了转换成流的数据,并传入了适配器。我也在添加我的活动代码。

ExploreActivity.kt

代码语言:javascript
运行
复制
class ExploreActivity : AppCompatActivity() {

    private val binding by lazy { ExploreLayoutBinding.inflate(layoutInflater) }
    val viewModel by viewModel<ExploreViewModel> {
        val list = intent?.getParcelableArrayListExtra(LIST_KEY) ?: emptyList<Category>()
        parametersOf(list)
    }
    var exploreAdapter = ExploreAdapter { topic -> handleNextActivity(topic) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        setupView()
    }

    fun setupView() {
        setupSearchView()
        setupFilteredTopic()
        setupExploreAdapter()
    }

    private fun setupFilteredTopic() {
        lifecycleScope.launchWhenCreated {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                viewModel.filteredTopics.collect { filteredTopicsList ->
                    exploreAdapter.submitList(filteredTopicsList)
                }
            }
        }
    }

    fun setupSearchView() {
        binding.searchView.apply {
            setOnQueryTextListener(object : SearchView.OnQueryTextListener {
                override fun onQueryTextSubmit(query: String?) = false
                override fun onQueryTextChange(newText: String?): Boolean {
                    return true
                }
            })
            
        }
    }

    fun setupExploreAdapter() {
        with(binding.exploreList) {
            adapter = exploreAdapter
        }
    }
}

更新2

ExploreViewModel.kt

代码语言:javascript
运行
复制
 val filteredCategories = query
        .debounce(200) // low debounce because we are just filtering local data
        .distinctUntilChanged()
        .combine(filteredTopics) { queryText, categoriesList ->
            val criteria = queryText.lowercase()
            if (criteria.isEmpty()) {
                    return@combine filteredTopics 
            } else {
                categoriesList.filter { category -> category.title?.lowercase()?.let { criteria.contains(it) } == true }
            }
        }

当我设置适配器时会出错。

固定

filteredTopics.value

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-06-12 13:50:50

您所链接的教程有一个由SearchView生成的流。如果希望将搜索功能保留在ViewModel中,可以将MutableStateFlow放入ViewModel中,由SearchView间接更新。可以公开用于更新查询的属性。

这有两种不同的方法,这取决于您(A)是否已经有了要快速查询的完整数据列表,或者(B)每次查询文本更改时都要查询服务器或数据库。

然后,甚至(A)也可以分解为:(A1)您有一个静态的普通旧列表,或者(A2)您的源列表来自一个流,例如不基于查询参数的返回的Room流。

下面的所有代码都在ViewModel类中。

A1:

代码语言:javascript
运行
复制
private val allCategories = categoriesList()
private val query = MutableStateFlow("")

// You should add an OnQueryTextListener on your SearchView that
// sets this property in the ViewModel
var queryText: String
    get() = query.value
    set(value) { query.value = value }

// This is the flow that should be observed for the updated list that 
// can be passed to the RecyclerView.Adapter.
val filteredCategories = query
    .debounce(200) // low debounce because we are just filtering local data
    .distinctUntilChanged()
    .map { 
        val criteria = it.lowercase()
        allCategories.filter { category -> criteria in category.title.lowercase }
    }

A2:

在本例中,我为上游服务器查询放置了一个简单的占位符流。这可能是任何流动。

代码语言:javascript
运行
复制
private val allCategories = flow {
    categoriesList()
}
private val query = MutableStateFlow("")

// You should add an OnQueryTextListener on your SearchView that
// sets this property in the ViewModel
var queryText: String
    get() = query.value
    set(value) { query.value = value }

// This is the flow that should be observed for the updated list that 
// can be passed to the RecyclerView.Adapter.
val filteredCategories = query
    .debounce(200) // low debounce because we are just filtering local data
    .distinctUntilChanged()
    .combine(allCategories) { queryText, categoriesList ->
        val criteria = queryText.lowercase()
        categoriesList.filter { category -> criteria in category.title.lowercase }
    }

B

代码语言:javascript
运行
复制
private val query = MutableStateFlow("")

// You should add an OnQueryTextListener on your SearchView that
// sets this property in the ViewModel
var queryText: String
    get() = query.value
    set(value) { query.value = value }

// This is the flow that should be observed for the updated list that 
// can be passed to the RecyclerView.Adapter.
val filteredCategories = query
    .debounce(500) // maybe bigger to avoid too many queries
    .distinctUntilChanged()
    .map { 
        val criteria = it.lowercase()
        categoriesList(criteria) // up to you to implement this depending on source
    }
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72588305

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档