How to use MutableStateFlow to search item in list

I am learning kotlin flow in android. I want to basically instant search in my list and filter to show in reyclerview. I searched in google and found this amazing medium post. This post is basically search from google. I want to search item in list and show in reyclerview. Can someone guide me how can I start this. I am explanning in more detail

Suppose I have one SearchBox and one Reyclerview which one item abc one, abc two, xyz one, xyz two... etc.

main image when all data is combine

enter image description here

Scenario 1

when I start typing in SearchBox and enter small a or capital A I want to show only two item matching in recyclerview, look like this

enter image description here

Scenario 2

when I enter any wrong text in SearchBox I want to basically show a text message that not found, look like this

enter image description here

Any guidance would be great. Thanks

I am adding my piece of code

ExploreViewModel.kt

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

<?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

@Parcelize
data class Category(
    val id: String? = null,
    val title: String? = null,
    val code: String? = null,
    val topics: List<Topics>? = null,
) : Parcelable

Topics.kt

@Parcelize
data class Topics(
    val id: String? = null,
    val title: String? = null
) : Parcelable

Dummy data and coming from server

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"))),
)

In my view model list is holding above dummy data. And In my recyclerview I am passing the whole object and I am doing flatMap to combine all data into list. Make sure In recyclerview is using Topic and using title property. In Image Abc one, Abc two is holding in Topic. Thanks

After @Tenfour04 suggestion I will go to A2 suggestion because I have already data which converted into flow and passing in my adapter. I am adding my activity code as well.

ExploreActivity.kt

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


Comments

Popular posts from this blog

Spring Elasticsearch Operations

Network Error and Timeout on Authorize.net JS

Object oriented programming concepts (OOPs)