I'm making a SearchView Filter for a RecyclerView, in my Filter object, in the fun 'publishResults', my app crash because i'm trying to modify a mutableListOf, but the data.clear() and data.addAll() cause an error
Every modification to the list seems to make the app crash, I've never seen this error despite all other list I've tried to modify. I think the problem come from the fact that it's used by the recyclerView but I have no clue why, I've find nothing on internet and even less with kotlin language.
Also it seems like there is a big problem when trying to cast the results?.values to a Collection.
here is my full Adapter code
class SmallCatchAdapter(val clickListener: FishModelListener) : RecyclerView.Adapter<SmallCatchAdapter.ViewHolder>(), Filterable {
var data = mutableListOf<FishModel>()
set(value) {
field = value
notifyDataSetChanged()
}
var dataFull = listOf<FishModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.bind(item, clickListener)
}
class ViewHolder private constructor(val binding: ItemListSmallCatchBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(item : FishModel, clickListener: FishModelListener) {
binding.fishModel = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object{
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemListSmallCatchBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
override fun getFilter(): Filter {
return fishListFilter
}
private val fishListFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val dataFiltered = mutableListOf<FishModel>()
if (constraint == null || constraint.isEmpty()) {
dataFiltered.addAll(dataFull)
} else {
val filterPattern: String = constraint.toString().toLowerCase().trim()
for (item in dataFull) {
if(item.name.toLowerCase().contains(filterPattern)) {
dataFiltered.add(item)
}
}
}
val results = FilterResults()
results.values = dataFiltered
return results
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
data.clear()
data.addAll(results?.values as MutableList<FishModel>)
notifyDataSetChanged()
}
}
}
class FishModelListener(val clickListener: (fishModelId: Int) -> Unit) {
fun onClick(fishModel: FishModel) = clickListener(fishModel.speciesID)
}
Where it crashes
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
data.clear()
data.addAll(results?.values as Collection<FishModel>)
notifyDataSetChanged()
}
The error:
java.lang.UnsupportedOperationException
at java.util.AbstractList.remove(AbstractList.java:161)
at java.util.AbstractList$Itr.remove(AbstractList.java:374)
at java.util.AbstractList.removeRange(AbstractList.java:571)
at java.util.AbstractList.clear(AbstractList.java:234)
Everything worked well before i implement this functionality, now I've also noticed that this bug has somehow messed with my Room Query, one (and only one) of my coroutineScope is never launched, everythings else is ok
(Sorry for the mistakes, i'm not english)
Instead of using data inside onBindViewHolder you should create another list variable and use it
var filterList : mutableListOf<FishModel>()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = filterList[position]
holder.bind(item, clickListener)
}
For the filter I am showing some references, follow a similar way as below:
private val customFilter: Filter = object : Filter() {
override fun performFiltering(constraint: CharSequence): FilterResults {
filterList =
if (constraint.isEmpty()) {
items.toMutableList()
} else {
items.filter {
it.name.contains(constraint, true) ||
it.name == constraint ||
it.price.toString() == constraint
}
.toMutableList()
}
if (sortByPV) {
filterList.sortByDescending { it.pointValue }
}
return FilterResults().apply {
values = filterList
count = filterList.size
}
}
override fun publishResults(constraint: CharSequence, results: FilterResults) {
notifyDataSetChanged()
}
}
override fun getFilter(): Filter = customFilter
override fun getItemCount() = filterList.size
Here, you may need to update your code accordingly for item click listener.
Related
I'm trying to fetch multiple documents from my Firestore collection so I can populate my RecyclerView. However, I'm getting a mismatch error when I try to hook my categories ArrayList to the QuerySnapshot, it says it's looking for kotlin.collections.ArrayList<Category> but it found Category?. What can I do to make my RecyclerView populate my category collection in Firestore? Do I need to rewrite my val categories = ArrayList<Category>()? Thank you!
Category Collection
Category.kt
data class Category(var category: String?, val categoryImage: String?) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(category)
parcel.writeString(categoryImage)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel)
}
override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size)
}
}
}
CategoryAdapter.kt
class CategoryAdapter(val category: ArrayList<Category>) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
var selectedCategory = Category("", "")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindCategory(category[position])
holder.itemView.setOnClickListener { v ->
val context: Context = v.context
val intent = Intent(context, CategoryServiceActivity::class.java)
selectedCategory.category = category[position].category
intent.putExtra("category", selectedCategory)
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return category.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.categoryrecyclyerview, parent, false)
return ViewHolder(view)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val categoryName = itemView.findViewById<TextView>(R.id.categoryJobNameTextView)
val categoryImage = itemView.findViewById<ImageView>(R.id.categoryImageView)
fun bindCategory(category: Category) {
categoryName?.text = category.category
Picasso.get().load(category.categoryImage).into(categoryImage)
}
}
}
HomeFragment.kt
val categories = ArrayList<Category>()
val categoriesDatabaseRef = FirebaseFirestore.getInstance().collection(REF_JOB_CATEGORIES)
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
for (querySnapshot in p0.documents) {
categories = querySnapshot.toObject(Category::class.java)
}
}
}
})
As I see in your code, the categories object is an ArrayList. So when you're using the following line of code:
categories = querySnapshot.toObject(Category::class.java)
It means that you're trying to convert the querySnapshot object, which is actually a DocumentSnapshot object, into an object of type Category, which works perfectly fine. However, you cannot assign that value to the categories object because between the ArrayList and Category classes, there is no inheritance relationship, hence that error.
So there are two ways in which you can solve this. The first solution would be to add an object of type Category, at each iteration of the for loop to the list:
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
for (querySnapshot in p0.documents) {
val category = querySnapshot.toObject(Category::class.java)
categories.add(category) //Add the object to the list.
}
}
}
})
The second solution, which is even simpler in my opinion, would be to convert the querySnapshot directly into a list, by removing the for loop like this:
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
categories = p0.toObjects(Category::class.java)
}
}
})
Please see that I have used toObjects(Class clazz) method which:
Returns the contents of the documents in the QuerySnapshot, converted to the provided class, as a list.
So it's toObjects, see the s? And not toObject.
Besides that, don't forget that Firebase API is asynchronous. So you cannot simply use the value of categories outside the onSuccess() method. If you're new to asynchronous programming, I recommend you read the following resource:
How to read data from Cloud Firestore using get()?
I am implementing filterable list for RecyclerView using ListAdapter with AsyncDifferConfig.Builder that implements Filterable. When searching and no result match, a TextView will be shown.
adapter.filter.filter(filterConstraint)
// Searched asset may not match any of the available item
if (adapter.itemCount <= 0 && adapter.currentList.isEmpty() && filterConstraint.isNotBlank())
logTxtV.setText(R.string.no_data)
else
logTxtV.text = null
Unfortunately the update of filter did not propagate immediately on adapter's count and list.
The adapter count and list is one step behind.
The TextView should be displaying here already
But it only shows after updating it back and the list is no longer empty at this point
I am not sure if this is because I am using AsyncDifferConfig.Builder instead of regular DiffCallback
ListAdapter class
abstract class FilterableListAdapter<T, VH : RecyclerView.ViewHolder>(
diffCallback: DiffUtil.ItemCallback<T>
) : ListAdapter<T, VH>(AsyncDifferConfig.Builder(diffCallback).build()), Filterable {
/**
* True when the RecyclerView stop observing
* */
protected var isDetached: Boolean = false
private var originalList: List<T> = currentList.toList()
/**
* Abstract method for implementing filter based on a given predicate
* */
abstract fun onFilter(list: List<T>, constraint: String): List<T>
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
return FilterResults().apply {
values = if (constraint.isNullOrEmpty())
originalList
else
onFilter(originalList, constraint.toString())
}
}
#Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
submitList(results?.values as? List<T>, true)
}
}
}
override fun submitList(list: List<T>?) {
submitList(list, false)
}
/**
* This function is responsible for maintaining the
* actual contents for the list for filtering
* The submitList for parent class delegates false
* so that a new contents can be set
* While a filter pass true which make sure original list
* is maintained
*
* #param filtered True if the list was updated using filter interface
* */
private fun submitList(list: List<T>?, filtered: Boolean) {
if (!filtered)
originalList = list ?: listOf()
super.submitList(list)
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
isDetached = true
}
}
RecyclerView Adapter
class AssetAdapter(private val glide: RequestManager, private val itemListener: ItemListener) :
FilterableListAdapter<AssetDataDomain, AssetAdapter.ItemView>(DiffUtilAsset()) {
inner class ItemView(itemView: AssetCardBinding) : RecyclerView.ViewHolder(itemView.root) {
private val assetName = itemView.assetName
private val assetPrice = itemView.assetPrice
private val assetMarketCap = itemView.assetMarketCap
private val assetPercentChange = itemView.assetPercentChange
private val assetIcon = itemView.assetIcon
private val assetShare = itemView.assetShare
// Full update/binding
fun bind(domain: AssetDataDomain) {
with(itemView.context) {
assetName.text = domain.symbol ?: domain.name
bindNumericData(
domain.metricsDomain.marketDataDomain.priceUsd,
domain.metricsDomain.marketDomain.currentMarketcapUsd,
domain.metricsDomain.marketDataDomain.percentChangeUsdLast24Hours
)
if (!isDetached)
glide
.load(
getString(
R.string.icon_url,
AppConfigs.ICON_BASE_URL,
domain.id
)
)
.into(assetIcon)
assetShare.setOnClickListener {
itemListener.onRequestScreenShot(
itemView,
getString(
R.string.asset_info,
domain.name,
assetPercentChange.text.toString(),
assetPrice.text.toString()
)
)
}
itemView.setOnClickListener {
itemListener.onItemSelected(domain)
}
}
}
// Partial update/binding
fun bindNumericData(priceUsd: Double?, mCap: Double?, percent: Double?) {
with(itemView.context) {
assetPrice.text = getString(
R.string.us_dollars,
NumbersUtil.formatFractional(priceUsd)
)
assetMarketCap.text = getString(
R.string.mcap,
NumbersUtil.formatWithUnit(mCap)
)
assetPercentChange.text = getString(
R.string.percent,
NumbersUtil.formatFractional(percent)
)
AppUtil.displayPercentChange(assetPercentChange, percent)
if (NumbersUtil.isNegative(percent))
assetPrice.setTextColor(Color.RED)
else
assetPrice.setTextColor(Color.GREEN)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemView =
ItemView(
AssetCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
override fun onBindViewHolder(holder: ItemView, position: Int) {
onBindViewHolder(holder, holder.absoluteAdapterPosition, emptyList())
}
override fun onBindViewHolder(holder: ItemView, position: Int, payloads: List<Any>) {
if (payloads.isEmpty() || payloads[0] !is Bundle)
holder.bind(getItem(position)) // Full update/binding
else {
val bundle = payloads[0] as Bundle
if (bundle.containsKey(DiffUtilAsset.ARG_PRICE) ||
bundle.containsKey(DiffUtilAsset.ARG_MARKET_CAP) ||
bundle.containsKey(DiffUtilAsset.ARG_PERCENTAGE))
holder.bindNumericData(
bundle.getDouble(DiffUtilAsset.ARG_PRICE),
bundle.getDouble(DiffUtilAsset.ARG_MARKET_CAP),
bundle.getDouble(DiffUtilAsset.ARG_PERCENTAGE)
) // Partial update/binding
}
}
// Required when setHasStableIds is set to true
override fun getItemId(position: Int): Long {
return currentList[position].id.hashCode().toLong()
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
isDetached = true
}
override fun onFilter(list: List<AssetDataDomain>, constraint: String): List<AssetDataDomain> {
return list.filter {
it.name.lowercase().contains(constraint.lowercase()) ||
it.symbol?.lowercase()?.contains(constraint.lowercase()) == true
}
}
interface ItemListener {
fun onRequestScreenShot(view: View, description: String)
fun onItemSelected(domain: AssetDataDomain)
}
}
UPDATE:
I can confirm that using DiffCallback instead of AsyncDifferConfig.Builder does not change the behavior and issue. It also seems that currentList is in async thus update on list does not reflect immediately after calling submitList.
I do not know if this is intended behavior but upon overriding onCurrentListChanged the currentList parameter is working correctly.
But the adapter.currentList is behaving like a previousList parameter
When you submit a list to recyclerView, it takes some time to compare items of current list and the previous one (to see if an item is removed, moved or added). so the result is not immediately ready.
you can use a RecyclerView.AdapterDataObserver to be notified of changes in recyclerView (it will tell what happened to items overall, like 5 were added etc)
P.S. if you look at recyclerView source code you will see that the DiffCallBack passed in the constructor, is wrapped in AsyncDifferConfig
I'm trying to add some search on the RecyclerView list without using
notifyDataSetChanged()
instead to it using
diffutil.callback()
but the issue is that it change the list correctly but it doesn't change the UI correctly
Here is my code and I will explain it
class RecordsAdapter : RecyclerView.Adapter<RecordsAdapter.ViewHolder>() {
var adapterList = listOf<CustomerModel>()
var modelList = listOf<CustomerModel>()
set(value) {
adapterList = value
field = value
}
private var modelListFiltered = listOf<CustomerModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(CustomerCellBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(adapterList[position])
}
override fun getItemCount(): Int = adapterList.size
fun filter(isFiltered: Boolean, filterSearch: String) {
if (isFiltered) {
val filter = modelList
.filter {
it.name.contains(filterSearch) || it.id.contains(filterSearch)
}
modelListFiltered = filter
}
adapterList = if (isFiltered) modelListFiltered else modelList
val diff = CartDiffUtil(
if (isFiltered) modelList else modelListFiltered,
if (isFiltered) modelListFiltered else modelList
)
DiffUtil.calculateDiff(diff).dispatchUpdatesTo(this)
}
inner class ViewHolder(private var binding: CustomerCellBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(model: CustomerModel) {
binding.let {
it.model = model
it.executePendingBindings()
}
}
}
}
class CartDiffUtil(private val oldList: List<CustomerModel>, private val newList: List<CustomerModel>) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].id == newList[newItemPosition].id
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition]
}
So I'm calling filter function to filter and I'm sending two parameters on if there is any filter and the second is the search.
Now the issue appears in this scenario
0. searching ""
1. searching "testing 2"
2. searching "testing 4"
3. searching "testing 2"
4. searching ""
As you can see in the images, when I search for "testing 2" after "testing 4" it keeps showing "testing 4" and even if I clear the search it gives me two cells of "testing 4" instead of one "testing 2" and one "testing 4"
Hope my question is clear.
Thanks.
I'm guessing your juggling of three list properties is leading to some situations where there can be the same list instance in the before and after of the DiffUtil so it cannot successfully compare them.
Also, it's much easier to use ListAdapter instead of RecyclerView.Adapter when you want to use DiffUtil. Note that when you use ListAdapter, you use ItemCallback instead of Callback. ItemCallback is simpler.
Try doing it this way, where there is only the modelList and when it or the filter changes, you determine what the new list is and submit it to the ListAdapter and let it handle the changes.
class RecordsAdapter : ListAdapter<CustomerModel, RecordsAdapter.ViewHolder>(CustomerModelCallback) {
var modelList = listOf<CustomerModel>()
set(value) {
field = value
resync()
}
private var filterText: String = ""
private var isFiltered: Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(CustomerCellBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(adapterList[position])
}
fun filter(isFiltered: Boolean, filterText: String = "") {
this.isFiltered = isFiltered
this.filterText = filterText
resync()
}
private fun resync() {
val newList = when {
isFiltered && filterText.isNotEmpty() ->
modelList.filter {
it.name.contains(filterSearch) || it.id.contains(filterSearch)
}
else -> modelList
}
submitList(newList)
}
// view holder...
}
object CustomerModelCallback : DiffUtil.ItemCallback<CustomerModel>() {
override fun areItemsTheSame(oldItem: CustomerModel, newItem: CustomerModel): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: CustomerModel, newItem: CustomerModel): Boolean =
oldItem == newItem
}
I have an Error in my array adapter filter section. Everything works fine when I try to enter value in autocomplete textview app crash and show me following error.
java.lang.NullPointerException: null cannot be cast to non-null type java.util.ArrayList
following are my code
class ProductSearchAdapter(
context: Context,
private var list: ArrayList<SaleFilterListModel>
) : ArrayAdapter<SaleFilterListModel>(context, 0, list){
#SuppressLint("ViewHolder")
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = ProductSearchListBinding.inflate(LayoutInflater.from(context), parent, false)
val item = list[position]
binding.searchProductName.text = item.productName
binding.searchProductRate.text = item.saleAmount
return binding.root
}
override fun getFilter(): Filter {
return productFilter
}
private val productFilter: Filter = object : Filter() {
override fun performFiltering(constraint: CharSequence): FilterResults {
val results = FilterResults()
val suggestions = ArrayList<SaleFilterListModel>()
if (constraint.isEmpty()) {
suggestions.addAll(list)
} else {
val filterPattern = constraint.toString().lowercase().trim()
for (item in list) {
if (item.productName.lowercase().contains(filterPattern)) {
suggestions.add(item)
}
}
}
results.values = suggestions
results.count = suggestions.size
return results
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
list = results?.values as ArrayList<SaleFilterListModel>
notifyDataSetChanged()
}
override fun convertResultToString(resultValue: Any?): CharSequence {
return (resultValue as SaleFilterListModel).productName
}
}
}
It is hard to tell without seeing the stack trace, but I would imagine that the publishResults function is the culprit here, as it is trying to cast what is a Nullable type (results?.values) to a Non-Nullable ArrayList<SaleFilterListModel>.
Certainly, this can be the case if publishResults is passed a null value for the results parameter.
I have Recycler View with list of Countries. On top of Recycler there is also Search View which is used to filtering countries and find without scrolling long list.
When I am typing in search and parallelly scrolling over list sometimes there is Error with Inconsistency Detected.
Fatal Exception: java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionCountryHolder{a35d5ed position=54 id=-1, oldPos=-1, pLpos:-1 no parent} androidx.recyclerview.widget.RecyclerView{1c30d63 VFED..... ......ID 0,410-1440,1899 #7f0a0028 app:id/SymbolRecyclerView}... androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5974)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6158)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
or this:
Fatal Exception: java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 14(offset:14).state:48 androidx.recyclerview.widget.RecyclerView{6df376e VFED..... ........ 0,410-1440,1899 #7f0a0028 app:id/SymbolRecyclerView}....
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6183)
at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
or this in GooglePLay test:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView{5ae0fff VFED..... ......ID 0,322-1080,1561 #7f0a0028 app:id/SymbolRecyclerView}....
at androidx.recyclerview.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:3051)
at androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:5536)
at androidx.recyclerview.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:12253)
at androidx.recyclerview.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:7354)
at RecyclerView_Adapter$getFilter$1.publishResults(RecyclerView_Adapter.kt:109)
I was googling about this and I found that I need to use notifyDataSetChanged() function and use temporary list for modyfing while searching but seems it is proper in my code. Can anybody help with fix ?
Below is my RecyclerView_Adapter code:
class RecyclerView_Adapter(private var countryList: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
var countryFilterList = ArrayList<String>()
lateinit var mcontext: Context
class CountryHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
init {
countryFilterList = countryList
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val countryListView =
LayoutInflater.from(parent.context).inflate(R.layout.custom_list_row, parent, false)
val sch = CountryHolder(countryListView)
mcontext = parent.context
return sch
}
override fun getItemCount(): Int {
return countryFilterList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.Symbol.text = countryFilterList[position]
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charSearch = constraint.toString()
if (charSearch.isEmpty()) {
countryFilterList = countryList
} else {
val resultList1 = ArrayList<String>()
for (row in countryList) {
if (row.contains(charSearch)) {
resultList1.add(row)
}
}
countryFilterList = ArrayList(resultList1.distinct())
}
val filterResults = FilterResults()
filterResults.values = countryFilterList
return filterResults
}
#Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
countryFilterList = results?.values as ArrayList<String>
notifyDataSetChanged()
}
}
}
Please try this Alternative solution.
Create Flowable from Original list
val countryListFlowable = Flowable.fromArray(countryList)
val consumer = Consumer<ArrayList<String>> {
//Here Search result will come as it
mAdapterRecycleView.updateList(it)
}
val disposableSearch = countryListFlowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).map { t: ArrayList<String> ->
t.filter {countryName: String ->
var isFound=false
if (countryName.contains(charSearch)) {
isFound = true
}
isFound
}
}.subscribe(consumer)
Your Adapter will have this method
fun updateList(list: ArrayList<String): {
countryFilterList = list
notifyDataSetChanged()
}