Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering RecyclerView/ListAdapter with SearchView

I'm trying to implement a filter for my RecyclerView. I use data-binding and my adapter is a ListAdapter subclass as shown below

class BookAdapter(private val clickListener: ClickHandler) :
    ListAdapter<Book, BookAdapter.ViewHolder>(BooksDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position)!!, clickListener)
    }

    class ViewHolder private constructor(val binding: BookItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(
            item: Book,
            clickListener: ClickHandler
        ) {
            binding.book = item
            binding.clickListener = clickListener
            binding.executePendingBindings()
        }

        companion object {
            fun from(parent: ViewGroup): ViewHolder {
                val inflater = LayoutInflater.from(parent.context)
                val binding = BookItemBinding.inflate(inflater, parent, false)
                return ViewHolder(binding)
            }
        }
    }
}

class BooksDiffCallback : DiffUtil.ItemCallback<Book>() {
    override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
        return oldItem == newItem
    }

}

class ClickHandler(val clickListener: (id: String) -> Unit) {
    fun onClick(item: Book) = clickListener(item.id)
}

According to the docs, to add filtering functionality I need to implement Filterable in my adapter and define getFilter() method. And this is where I stuck: I simply don't know how to implement getFilter() in the case of ListAdapter. Any help will be appreciated.

like image 906
Alex Avatar asked Oct 10 '19 09:10

Alex


1 Answers

I had a similar problem and tried solving it using a method similar to the one described by Maor Hadad above. it worked at some times and it raised cast error in the

Filter.publishResult()

method. So, I solved it this way. Firstly create a variable private var unfilteredlist = listOf<BaseDataItem>() and a methods

fun modifyList(list : List<BaseDataItem>) {
    unfilteredList = list
    submitList(list)
}

fun filter(query: CharSequence?) {
    val list = mutableListOf<BaseDataItem>()

    // perform the data filtering
    if(!query.isNullOrEmpty()) {
        list.addAll(unfilteredList.filter {
            it.*field1*.toLowerCase(Locale.getDefault()).contains(query.toString().toLowerCase(Locale.getDefault())) ||
                    it.*field2*.toLowerCase(Locale.getDefault()).contains(query.toString().toLowerCase(Locale.getDefault())) })
    } else {
        list.addAll(unfilteredList)
    }

    submitList(list)
}

in the BookAdapter class. Where *field1* and *field2*(you can add more fields) are the fields you want the search query to match. Then, wherever you call the adapter.submitList(List<BaseDataItem>) in the original code, replace it with the custom method adapter.modifyList(List<BaseDataItem>). Then write the searchView.setOnQueryTextListener like the one below

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

        return false
    }

    override fun onQueryTextChange(newText: String): Boolean {

        (binding.recycler.adapter as ItemAdapter).filter(newText)
        return true
    }
})

Don't forget to remove the Filterable interface and its methods, you don't need them anymore

like image 50
ilatyphi95 Avatar answered Sep 18 '22 09:09

ilatyphi95