In order to support filtering items in our RecyclerView, we must change a few things. Take our CustomAdapter from 2.3 Implementation of a Recycler View:
classCustomAdapter(privateval dataSet: List<Book>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {/** * Provide a reference to the type of views that you are using * (custom ViewHolder). */classViewHolder(view: View) : RecyclerView.ViewHolder(view) {val bookName: TextView= view.findViewById(R.id.book_name)val author: TextView= view.findViewById(R.id.author)val publisher: TextView= view.findViewById(R.id.publisher) }// Create new views (invoked by the layout manager)overridefunonCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {// Create a new view, which defines the UI of the list itemval view = LayoutInflater.from(viewGroup.context) .inflate(R.layout.book_row_item, viewGroup, false)returnViewHolder(view) }// Replace the contents of a view (invoked by the layout manager)overridefunonBindViewHolder(viewHolder: ViewHolder, position: Int) {// Get element from your dataset at this position and replace the// contents of the view with that element viewHolder.bookName.text = dataSet[position].bookName viewHolder.author.text = dataSet[position].author viewHolder.publisher.text = dataSet[position].publisher }// Return the size of your dataset (invoked by the layout manager)overridefungetItemCount() = dataSet.size}
In the RecyclerView adapter, create an ArrayList with the name dataSetFiltered, and pass all the items from our original list. The rest of the adapter is now based off of this filtered list as opposed to the original data set (which should remain unchanged, acting as a source of truth for what an unfiltered list looks like):
Now, in the onBindViewHolder get the item for each row from the dataSetFiltered list:
classCustomAdapter(privateval dataSet: List<Book>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {privatevar dataSetFiltered: List<Book> = dataSet...// Replace the contents of a view (invoked by the layout manager)overridefunonBindViewHolder(viewHolder: ViewHolder, position: Int) {// Get element from your dataset at this position and replace the// contents of the view with that element viewHolder.bookName.text = dataSetFiltered[position].bookName viewHolder.author.text = dataSetFiltered[position].author viewHolder.publisher.text = dataSetFiltered[position].publisher }overridefungetItemCount(): Int= dataSetFiltered.size}
Lastly, add the capabilities to filter based on some query!
Add the filter variable to your adapter:
classCustomAdapter(privateval dataSet: List<Book>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {privatevar dataSetFiltered: List<Book> = dataSetvar filterTest: CharSequence=""set(value) {field=valueonFilterChange() }...// Replace the contents of a view (invoked by the layout manager)overridefunonBindViewHolder(viewHolder: ViewHolder, position: Int) {// Get element from your dataset at this position and replace the// contents of the view with that element viewHolder.bookName.text = dataSetFiltered[position].bookName viewHolder.author.text = dataSetFiltered[position].author viewHolder.publisher.text = dataSetFiltered[position].publisher }overridefungetItemCount(): Int= dataSetFiltered.size}
Next, we have to implement onFilterChange:
classCustomAdapter(privateval dataSet: List<Book>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {privatevar dataSetFiltered: List<Book> = dataSetvar filterTest: CharSequence=""set(value) {field=valueonFilterChange() }privatefunonFilterChange() { dataSetFiltered =if (charString.isEmpty()) {// There's no query, should return back the unfiltered list. dataSet } else {// The filter function returns a list containing only elements // matching the given predicate. In this case, we can choose // our predicate to be what we want to filter by. Since we are working// with books, let's allow filtering by author and title! dataSet.filter { book -> book.bookName.contains(charString) || book.author.contains(charString) } }notifyDataSetChanged() }// Replace the contents of a view (invoked by the layout manager)overridefunonBindViewHolder(viewHolder: ViewHolder, position: Int) {// Get element from your dataset at this position and replace the// contents of the view with that element viewHolder.bookName.text = dataSetFiltered[position].bookName viewHolder.author.text = dataSetFiltered[position].author viewHolder.publisher.text = dataSetFiltered[position].publisher }overridefungetItemCount(): Int= dataSetFiltered.size}
Using notifyDataSetChanged() is costly and often inefficient. We can take advantage of the DiffUtil Android utility class to make updating our RecyclerView more efficient.
Using the Filter
To use filter, we can simply set the value of the filter in our adapter:
adapter.filter = someText
We can also leverage SearchView to filter our RecyclerView by query from the user.