'Restoring the state of a SearchView and filtering LiveData with the restored state

I'm using a SearchView in my layout to let a user search for categories. The problem that arises is when restoring state when i navigate away from the fragment and come back (View gets restored)...

What happens is that the onQueryTextChange method gets called from the SearchView's setOnQueryTextListener with its last entered query, before submitList() is called. The result of this is an empty list instead of a filtered list.

The question: How can I make sure onQueryTextChange gets called (with restored state) after submitlist has been called, without breaking anything

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            return false
        }

        override fun onQueryTextChange(newText: String?): Boolean {
            (binding.rvCategories.adapter as CategoriesAdapter).filter.filter(newText)
            return false
        }
    })

    viewModel.categories.observe(viewLifecycleOwner) {
        categoryAdapter.submitList(it)
    }
}

I'm not sure how to solve this problem. I want the SearchView to restore its state and filter the list after it has received its data. I have tried filtering using my ViewModel but the problem in this scenario is that i can't use a SwitchMap function for the categories LiveData because i already have one.

The only 'solution' I have now is to disable the SearchView from restoring its state by doing:

searchView.isSaveEnabled = false
searchView.isSaveFromParentEnabled = false

But that's not really what i want because I'd like the fragment to restore its state correctly

My ViewModel:

    private val _rootCategory = MutableLiveData<Category?>()
    val rootCategory: LiveData<Category?>
        get() = _rootCategory

    val categories = rootCategory.switchMap {
        loadData(it)
    }

    private fun loadData(rootCategory: Category?): LiveData<List<Category>> {
        return if (rootCategory == null) {
        //No root category, retrieve root categories
        databaseManager.getRootCategories().asLiveData().map {
            removeDefaultCategory(it)
        }
    } else {
        //Root category, retrieve its subcategories
        databaseManager.getRootCategories().asLiveData().map {
            it.first { c -> c == rootCategory }.subCategories
        }
    }
}


Solution 1:[1]

IMHO it is better if you filter categories in ViewModel and then submit the filtered data to categoryAdapter instead of directly calling filter in CategoriesAdapter. This way, every time your view gets restored you have the latest data from categories LiveData gets submitted to categoriesAdapter and also you applying Unidirectional Data Flow. Something like this

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            return false
        }

        override fun onQueryTextChange(newText: String?): Boolean {
            // notify viewModel of text change
            viewModel.onQueryTextChange(newText ?: "")
            return false
        }
    })

    viewModel.categories.observe(viewLifecycleOwner) {
        categoryAdapter.submitList(it)
    }
}

then in your ViewModel

fun onQueryTextChange(newQuery: String) {
    // get filtered categories from database or
    // filter categories list and then emit the
    // new list using categories LiveData
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Amin Mousavi