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:
Searching something:
Now I delete the entire text in the searchView
:
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.
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()
}