Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Filterable in ListAdapter using Kotlin?

I would use a SearchView to filter my RecyclerView, on stackoverflow and other sites i've found just examples of using Filterable with Java and with RecyclerView.Adapter while i'm using ListAdapter..

So i was trying to make the custom filter by my self but when i try to filter the adapter i just get a null on my MutableList in publishResults.

My Adapter code looks like this:

class ArticoliListAdapter : ListAdapter<Articolo, ArticoliListAdapter.ArticoliViewHolder>(ArticoliComparator()), Filterable {
    private val list = mutableListOf<Articolo>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticoliViewHolder {
        return ArticoliViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: ArticoliViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(current)
    }

    override fun getItemId(position: Int): Long {
        val articolo = currentList[position]
        return articolo.barcode.hashCode().toLong()
    }




    class ArticoliViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val barcode: TextView = itemView.findViewById(R.id.barcode)
        private val qta: TextView = itemView.findViewById(R.id.qta)
        private val desc: TextView = itemView.findViewById(R.id.desc)
        private val um: TextView = itemView.findViewById(R.id.um)
        fun bind(articolo: Articolo?) {
            barcode.text = articolo?.barcode
            qta.text = articolo?.qta?.formatForQta()
            um.text = articolo?.um?.toLowerCase(Locale.ITALIAN)
            desc.text = if(articolo?.desc.isNullOrEmpty()) "-" else articolo?.desc
        }

        private fun Float.formatForQta(): String {
            val floatString = this.toString()
            val decimalString: String = floatString.substring(floatString.indexOf('.') + 1, floatString.length)
            return when (decimalString.toInt() == 0) {
                true -> this.toInt().toString()
                false -> "%.3f".format(this)
            }
        }

        companion object {
            fun create(parent: ViewGroup): ArticoliViewHolder {
                val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_layout, parent, false)
                return ArticoliViewHolder(view)
            }
        }
    }

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

        override fun areContentsTheSame(oldItem: Articolo, newItem: Articolo): Boolean {
            return oldItem.qta == newItem.qta
        }
    }

    override fun getFilter(): Filter {
        return customFilter
    }

    private val customFilter = object: Filter() {
        override fun performFiltering(constraint: CharSequence?): FilterResults {
            val filteredList = mutableListOf<Articolo>()
            if (constraint == null || constraint.isEmpty()){
                filteredList.addAll(currentList)
            }else {
                val filterPattern = constraint.toString().toLowerCase(Locale.ITALIAN).trim { it <= ' ' }
                for (item in currentList) {
                    if (item.barcode.toLowerCase(Locale.ITALIAN).contains(filterPattern) || item.desc?.toLowerCase(
                            Locale.ITALIAN
                        )!!.contains(filterPattern)) {
                        filteredList.add(item)
                    }
                }
            }
            val results = FilterResults()
            results.values = filteredList
            return results
        }

        override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
            list.clear()
            list.addAll(filterResults?.values as MutableList<Articolo>)
            notifyDataSetChanged()
        }

    }

}

So i was wondering which would be the right way to built a custom filter to filter my data in the recyclerView by using ListAdapter in Kotlin.

I'm calling the filter in my fragment like this:

    override fun onQueryTextChange(query: String?): Boolean {
        adapter.filter.filter(query)
        return false
    }

But when i try to filter nothing happend and still all items are shown...

Data to the RecyclerView adapter is set from my ViewHolder and the data is get from the DataBase (LiveData<List<Articolo>>)

Here is the code from my Fragment:

   articoliViewModel.articoli.observe(viewLifecycleOwner) { articoli ->
        articoli.let { adapter.submitList(it) }
    }
like image 422
NiceToMytyuk Avatar asked Dec 30 '22 17:12

NiceToMytyuk


1 Answers

Few flaws in your code which i am listing down below.

  1. currentList is holding the current items which r on list not the complete list of items . i.e if you have 10 items and after filter u get 3 items then currentList will be holding 3 items not 10 . So you can not use currentList for filtering the list . instead u hold on to the CompleteList and apply filter on this one .

  2. you should not be calling notifyDataSetChanged() this just defeats the whole purpose of having DiffUtils, instead you call #submitList

  3. Al thought you have a reference to complete list as global variable but you have never assigned value to it its always empty.

I have made a working sample to illustrate. pls try same with your code adding the essential code below. I have use type as String just to make sample easy to understand you can use your custom object. You can also modify the code to make it look better but i think its enough to get the idea how ListAdapter works.

class ArticoliListAdapter : ListAdapter<String, ArticoliListAdapter.ArticoliViewHolder>(ArticoliComparator()), Filterable {
    private var list = mutableListOf<String>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticoliViewHolder {
        return ArticoliViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: ArticoliViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(current)
    }

    fun setData(list: MutableList<String>?){
        this.list = list!!
        submitList(list)
    }

    class ArticoliViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val desc: TextView = itemView.findViewById(R.id.txtName)
        fun bind(name: String) {
            desc.text = name.toUpperCase()
        }

        companion object {
            fun create(parent: ViewGroup): ArticoliViewHolder {
                val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_list, parent, false)
                return ArticoliViewHolder(view)
            }
        }
    }

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

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

    override fun getFilter(): Filter {
        return customFilter
    }

    private val customFilter = object : Filter() {
        override fun performFiltering(constraint: CharSequence?): FilterResults {
            val filteredList = mutableListOf<String>()
            if (constraint == null || constraint.isEmpty()) {
                filteredList.addAll(list)
            } else {
                for (item in list) {
                    if (item.toLowerCase().startsWith(constraint.toString().toLowerCase())) {
                        filteredList.add(item)
                    }
                }
            }
            val results = FilterResults()
            results.values = filteredList
            return results
        }

        override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
            submitList(filterResults?.values as MutableList<String>)
        }

    }
}

When you set data to adapter you call setData not submitList.

articoliViewModel.articoli.observe(viewLifecycleOwner) { articoli ->
    articoli.let { adapter.setData(it) }
}
like image 61
ADM Avatar answered Jan 02 '23 07:01

ADM