Skip to content

Instantly share code, notes, and snippets.

@UbadahJ
Last active May 5, 2024 02:25
Show Gist options
  • Save UbadahJ/99818433d0ea72f728d66417a7d52afc to your computer and use it in GitHub Desktop.
Save UbadahJ/99818433d0ea72f728d66417a7d52afc to your computer and use it in GitHub Desktop.
A filterable ListAdapter for RecyclerView

Filterable List Adapter

These are the steps to use the adapter.

  1. Replace the adapter extending ListAdapter with FilterableListAdapter.
  2. Implement the method onFilter that provides a List and the string the was passed to the filter method
  3. Update the list according the constraint

Usage

val filterAdapter = null // Your adapter instance here
filterAdapter.filter.filter(/*Pass your string here*/)
abstract class FilterableListAdapter<T, VH : RecyclerView.ViewHolder>(
diffCallback: DiffUtil.ItemCallback<T>
) : ListAdapter<T, VH>(diffCallback), Filterable {
private var originalList: List<T> = currentList.toList()
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)
}
abstract fun onFilter(list: List<T>, constraint: String): List<T>
/**
* 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)
}
}
@ArcherEmiya05
Copy link

ArcherEmiya05 commented Mar 2, 2022

Regarding the filter callback I want to know if upon using filter, the list became empty so I can show a message to user. Does checking adapter.currentList.isEmpty() will do this or the currenList represent the original list thus will not be empty after applying a filter?

@ArcherEmiya05
Copy link

ArcherEmiya05 commented Mar 2, 2022

@ArcherEmiya05 Please check if the README answers your question. The implementation has been updated to remove dynamic filter support since that is not a common use case so unless you need that use the new implementation. The steps for both remain the same.
Regarding the callback, it depends on why do you need it and where you need it.

Thanks for the update, this is a real help. One last thing, about the constraint for filter. How can we set them just like the sample with User object where I want to filter its name attribute only. Does this current implementation filter against all the String data type attribute of an object? Is that what you mean about dynamic filter support?

Oohh so I can specify this by overriding the onFilter on the child class?

Is this correct or there is more optimal way?

override fun onFilter(list: List<AssetDomainData>, constraint: String): List<AssetDomainData> {

      return list.filter {
          it.name.lowercase().contains(constraint.lowercase()) || it.symbol?.lowercase()
              ?.contains(constraint.lowercase()) == true
      }

  }

@ArcherEmiya05
Copy link

Hello, how can we use this with header itemview? It seems ListAdapter by default does not support it and after trying it the list behaves weird as it starts at the bottom. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment