Search code examples
androidkotlinmvvmandroid-recyclerviewsearchview

MVVM Recyclerview Livedata Room SearchView


I am making database app using room, mvvm, livedata . I have Prepopulated it with some data. Now i have two options either i am going to show that prepoulated data when app turns on via recyclerview or show it when i search it using SearchView inside recyclerView.
The Problem is when i search for particular item, itw shows that item when i complete my word and when i try to go back either my resView back empty or it always stays on that item. i wanna try sometime realtime like: when i enter only one alphabet it show all the suggestions belongs to alphabet, i only want to update with livedata
What i have try?
1-> I have already tried switchMap which worked with liveData but i have to refresh my activity in order to get back my list.
2-> I have tried resView filter which didn't worked because i am using livedata to update my UI and also give it regular try without livedata it still didn't work either.
3-> I Have tried regular editText just to try but i didn't find it useful as facing same problem, my main focus was on searchView
RecyclerView code with filterable or just ignore filterable part it wasn't good try at all

class MyAdapter(
    private var context: Context,
    private var dataList: List<SearchPojo>
) : RecyclerView.Adapter<MyAdapter.BaseViewHolder<*>>(), Filterable {

    private var exampleListFull: List<SearchPojo>? = null

    init {
        exampleListFull = ArrayList(dataList)

    }

    companion object {
        const val SEARCH_TYPE = 1
    }

    abstract class BaseViewHolder<T>(view: View) : RecyclerView.ViewHolder(view) {
        abstract fun bind(t: T)
    }

    inner class SearchViewHolder(view: View) : BaseViewHolder<SearchPojo>(view) {
        private val userID: TextView = view.findViewById(R.id.idSearch)
        private val userName: TextView = view.findViewById(R.id.nameSearch)
        private val userPos: TextView = view.findViewById(R.id.positionSearch)

        override fun bind(t: SearchPojo) {
            userID.text = t.id
            userName.text = t.userName
            userPos.text = t.position
        }
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
        return when (viewType) {
            SEARCH_TYPE -> {
                val view =
                    LayoutInflater.from(context).inflate(R.layout.layout_show_data, parent, false)
                SearchViewHolder(view)
            }
            else -> {
                throw IllegalAccessException("In valid View Type")
            }
        }
    }

    override fun getItemCount(): Int {
        return dataList.size
    }

    override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
        val element = dataList[position]
        when (holder) {
            is SearchViewHolder -> {
                holder.bind(element)
            }
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when (dataList[position]) {
            is SearchPojo -> SEARCH_TYPE

            else -> throw IllegalAccessException()
        }
    }

    override fun getFilter(): Filter {
        return exampleFilter
    }

    private var exampleFilter = object : Filter() {

        override fun performFiltering(constraint: CharSequence?): FilterResults {
            val filteredList = ArrayList<SearchPojo>()

            if (constraint == null || constraint.isEmpty()) {
                filteredList.addAll(exampleListFull as Iterable<SearchPojo>)
            } else {
                val filterPattern = constraint.toString().toLowerCase().trim { it <= ' ' }

                for (item in exampleListFull!!) {
                    if (item.userName.toLowerCase().contains(filterPattern)) {
                        filteredList.add(item)
                    }
                }
            }

            val results = FilterResults()
            results.values = filteredList

            return results
        }

        override fun publishResults(constraint: CharSequence, results: FilterResults) {
            dataList.clear()
            dataList.addAll(results.values as List<SearchPojo>)
            notifyDataSetChanged()
        }
    }

}

Main Activity

class MainActivity : AppCompatActivity() {

    lateinit var searchViewModel: SearchViewModel
    lateinit var myAdapter: MyAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (!::searchViewModel.isInitialized) {
            searchViewModel = ViewModelProviders.of(this)[SearchViewModel::class.java]
            searchViewModel.getAll().observe(this, Observer {
                myAdapter(it)
            })

        }

        searchItems.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                nameFromDb(s.toString())
            }
        })
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.search_item, menu)

        val searchItem = menu!!.findItem(R.id.search_menu)
        val searchView = searchItem.actionView as SearchView


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

            override fun onQueryTextChange(newText: String?): Boolean {
                myAdapter.getFilter().filter(newText)
                return true
            }
        })


        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        when (item!!.itemId) {
            R.id.refresh -> {
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent)
            }
        }
        return true
    }


    private fun nameFromDb(searchTxt: String) {
        searchViewModel = ViewModelProviders.of(this)[SearchViewModel::class.java]
        searchViewModel.items.observe(this, object : Observer<List<SearchPojo>> {
            override fun onChanged(t: List<SearchPojo>?) {
                if (t == null) {
                    return
                }
                myAdapter(t)
            }
        })

        searchViewModel.searchIt(searchTxt)

    }

    private fun myAdapter(t: List<SearchPojo>) {
        searchResultResView.apply {
            layoutManager = LinearLayoutManager(context)
            myAdapter = MyAdapter(this@MainActivity, t)
            adapter = myAdapter

        }
    }
}

ViewModel

lass SearchViewModel(application: Application) :
    AndroidViewModel(application) {
    private val repo: SearchRepo = SearchRepo(application)

    private val _searchItem : MutableLiveData<String> = MutableLiveData()

    val items : LiveData<List<SearchPojo>> = Transformations.switchMap(_searchItem) { myItems ->
        repo.searchItem(myItems)
    }

    init {
            _searchItem.value = ""
    }

    fun searchIt(items: String) {
        _searchItem.value = items
    }

    fun getAll() = repo.allSearch()
}

Repository

class SearchRepo(application: Application) {

    private val searchDao: SearchDao


    init {
        val db = SearchDb.instance(application)
        searchDao = db.searchDao()

    }


    fun searchItem(id: String): LiveData<List<SearchPojo>> {
        return searchDao.searchViaID(id)
    }

    fun allSearch() : LiveData<List<SearchPojo>> {
        return searchDao.allSearch()
    }


}

Dao

@Dao
abstract class SearchDao : BaseDao<SearchPojo> {

    @Query("Select * from SearchPojo")
    abstract fun allSearch(): LiveData<List<SearchPojo>>

    @Query("Select * from SearchPojo where userName Like :userNameSearch or LOWER(userName) like LOWER(:userNameSearch)")
    abstract fun searchViaID(userNameSearch: String) : LiveData<List<SearchPojo>>

    @Insert
    abstract override fun insert(searchPojo: SearchPojo)

}

Solution

  • Please change your @Dao class like this

    @Dao
    abstract class SearchDao : BaseDao<SearchPojo> {
    
        @Query("Select * from SearchPojo")
        abstract fun allSearch(): LiveData<List<SearchPojo>>
    
        @Query("Select * from SearchPojo where userName GLOB '*' || :userNameSearch|| '*'")
        abstract fun searchViaID(userNameSearch: String) : LiveData<List<SearchPojo>>
    
        @Insert
        abstract override fun insert(searchPojo: SearchPojo)
    
    }