androidkotlinsearchviewandroid-filterable

When I search in the list of the recyclerview it removes items into original recyclerview list


I have the next recyclerview adapater which implements the Filterable class to try to search inside my productList and update the recyclerview.

class ProductsAdapterV2(
    private val itemClickListener: OnProductClickListener
) : RecyclerView.Adapter<BaseViewHolder<*>>(), Filterable {

    private var productsList = listOf<Product>()
    private var productsListBackup = listOf<Product>()

    interface OnProductClickListener {
        //Método para gestionar el OnClick de los items del recyclerview
        fun onProductClick(product: Product)
        //Método para gestionar el click largo cuando se toca el item por unos segundos..
        fun onProductLongClick(product: Product, position: Int)
    }

    fun setProductList(productsList: List<Product>) {
        this.productsList = productsList
        this.productsListBackup = productsList

        notifyDataSetChanged()
    }
    fun getProductList():List<Product>{
        return this.productsList
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
        val itemBinding = ProductsListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        val holder = MainViewHolder (itemBinding)

        holder.itemView.setOnClickListener {
            val position = holder.adapterPosition.takeIf { it != DiffUtil.DiffResult.NO_POSITION }
                    ?: return@setOnClickListener
            itemClickListener.onProductClick(productsList[position])
        }

        holder.itemView.setOnLongClickListener {
            val position = holder.adapterPosition.takeIf { it != NO_POSITION }
                ?: return@setOnLongClickListener true

            itemClickListener.onProductLongClick(productsList[position], position)

            return@setOnLongClickListener true
        }

        return holder
    }

    override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
        when (holder) {
            is MainViewHolder  -> holder.bind(productsList[position])
        }
    }

    override fun getItemCount(): Int = productsList.size

    private inner class MainViewHolder (val binding: ProductsListBinding) : BaseViewHolder<Product>(binding.root) {
        override fun bind(item: Product) {
            val description = if (item.description.isNotEmpty()) item.description else "Sin descripción."
            val barcode = if (item.barcode.isNotEmpty()) item.barcode  else "Sin código."

            binding.txtName.text=item.name
            binding.txtDescription.text=description
            binding.txtBarcode.text=barcode
            binding.txtQty.text = "Cantidad: ${item.qty}"
            binding.txtPrice.text = "S/${item.price}"
            //Si el image_bitmap no es nulo:
            item.image_bitmap?.let { binding.productImage.setImageBitmap(item.image_bitmap!!) }

        }
    }

    //Filterable methods to search

    override fun getFilter(): Filter {
        return object : Filter() {

            override fun performFiltering(charSequence: CharSequence?): Filter.FilterResults {
                val queryString = charSequence?.toString()?.lowercase()

                val filterResults = Filter.FilterResults()
                filterResults.values = if (queryString==null || queryString.isEmpty())
                    productsList
                else
                    productsList.filter {
                        it.name.lowercase().contains(queryString) ||
                                it.barcode.lowercase().contains(queryString) ||
                                it.description.lowercase().contains(queryString)
                    }
                return filterResults
            }

            override fun publishResults(charSequence: CharSequence?, filterResults: Filter.FilterResults) {
                productsList = filterResults.values as List<Product>
                notifyDataSetChanged()
               
            }
        }
    }

}

To seach I'm using one SearchView:

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

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

                return false
            }
        })
    }

However the search task works well at the begining, but after have searched something the entire productList is updated and now has less data than before, I can see this rare behavior when I type something and then I delete some words --The list has now less data--

Example:

Initial list:

enter image description here

Searching something:

enter image description here

Now I delete the entire text in the searchView:

enter image description here

We can see that the recyclerview is not updated with the original data and now has only the items of the previous search.

I would like to improve the function getFilter() of the recyclerview adapter because I think that on there is the problem or maybe inside of the method publishResults().

Any idea guys to fix this problem I will appreciate it. Thanks in advance.


Solution

  • You already have a backup list, which is good, but you need to be using that backup list as the source of the full list of items, or else your filter will keep filtering its previous output instead of the original full list of items.

    Also, a tip. Use String?.orEmpty() to avoid having to deal with nullable values more than you have to.

    override fun performFiltering(charSequence: CharSequence?): Filter.FilterResults {
        val queryString = charSequence?.toString().orEmpty().lowercase()
        return Filter.FilterResults().apply {
            values = when {
                    queryString.isEmpty() -> productsListBackup
                    else -> productsListBackup.filter {
                        it.name.lowercase().contains(queryString) ||
                        it.barcode.lowercase().contains(queryString) ||
                        it.description.lowercase().contains(queryString)
                }
        }
    }
    

    Side note: In Kotlin, you should not create get...() and set...() functions. You already have properties, so getters and setters are implicit. If you need to do anything complicated, you can define a custom getter or setter behavior. So in your case, you can remove getProductList() and setProductList() and update your existing property to be public and look like:

    var productsList = listOf<Product>()
        set(value) {
            field = value
            productsListBackup = productsList
            notifyDataSetChanged()
        }