Best practice: Runtime filters with Room and LiveData

后端 未结 3 431
傲寒
傲寒 2021-02-02 11:24

I am working on a screen that shows the contents of a Room wrapped DB using a recycler. The adapter gets the LiveData from a ViewModel that hides the query call on the Room DAO

3条回答
  •  醉梦人生
    2021-02-02 11:26

    Based on Francisco's answer (thank you VERY much for that!), here is how I implemented similar dynamic database filtering based on EditText input, but in Kotlin.

    Here is the Dao query example, where I perform a select based on a passed in filter String:

    // Dao query with filter
    @Query("SELECT * from myitem WHERE name LIKE :filter ORDER BY _id")
    fun getItemsFiltered(filter: String): LiveData>
    

    I have a repository, but in this case it's just a simple pass-through. If you don't have a repository, you could call the dao method directly from the ViewModel.

    // Repository
    fun getItemsFiltered(filter: String): LiveData> {
        return dao.getItemsFiltered(filter)
    }
    

    And then in the ViewModel I use the Transformations method that Francisco also used. My filter however is just a simple String wrapped in MutableLiveData. The setFilter method posts the new filter value, which in turn causes allItemsFiltered to be transformed.

    // ViewModel
    var allItemsFiltered: LiveData>
    var filter = MutableLiveData("%")
    
    init {
        allItemsFiltered = Transformations.switchMap(filter) { filter ->
            repository.getItemsFiltered(filter)
        }
    }
    
    // set the filter for allItemsFiltered
    fun setFilter(newFilter: String) {
        // optional: add wildcards to the filter
        val f = when {
            newFilter.isEmpty() -> "%"
            else -> "%$newFilter%"
        }
        filter.postValue(f) // apply the filter
    }
    

    Note the initial filter value is set to a wildcard ("%") to return all items by default. If you don't set this, no items will be observed until you call setFilter.

    Here is the code in the Fragment where I observe the allItemsFiltered and also apply the filtering. Note that I update the filter when my search EditText is changed, and also when the view state is restored. The latter will set your initial filter and also restore the existing filter value when the screen rotates (if your app supports that).

    // Fragment
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        // observe the filtered items
        viewModel.allItemsFiltered.observe(viewLifecycleOwner, Observer { items ->
            // update the displayed items when the filtered results change
            items.let { adapter.setItems(it) }
        })
    
        // update the filter as search EditText input is changed
        search_et.addTextChangedListener {text: Editable? ->
            if (text != null) viewModel.setFilter(text.toString())
        }
    }
    
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
    
        // update the filter to current search text (this also restores the filter after screen rotation)
        val filter = search_et.text?.toString() ?: ""
        viewModel.setFilter(filter)
    
    }
    

    Hope that helps!!

    Disclaimer: this is my first post, so let me know if I missed something. I'm not sure how to link to Francisco's answer, otherwise I would have done that. It definitely helped me get to my implementation.

提交回复
热议问题