Search Filter on Recycler View (Android)

Mobin Munir
AndroidPub
Published in
4 min readJun 19, 2019

--

Android Dynamic Search Adapter

Hey guys. I am back for my 3rd article here on medium. Its a short one but very helpful if you frequently need to develop search features for your listings through out your app. So I came up with a great way which involved developing an abstract dynamic adapter implementation which can be extended to use the search feature for any number of your required listings.

Pre-Requisites:

Generics, Interface, Pillars of OOP, Kotlin (Lambdas/High Order Functions), AndroidX Artifacts and Recycler View.

Let’s get Started

First, You will need to create a recycler view adapter to handle your listing. However it will extend DynamicSearchAdapter instead of standard one.

When you extend it, you will provide your list to the parent adapter and also the type of list in <> as type parameter and that’s it you can now use the search feature in your any number of lists in activities/fragments.

For example the basic implementations for your 2 different adapters would look like these below.

class SearchAdapter1(private val mutableList: MutableList<SearchModel1>) :
DynamicSearchAdapter<SearchModel1>(mutableList) {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val textView = TextView(parent.context)
return ViewHolder(textView)
}

override fun getItemCount(): Int {
return mutableList.count()
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tv = holder.containerView as TextView
tv.text = mutableList[position].data

}


}
class SearchAdapter2(private val mutableList: MutableList<SearchModel2>) :
DynamicSearchAdapter<SearchModel2>(mutableList) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val textView = TextView(parent.context)
return ViewHolder(textView)
}

override fun getItemCount(): Int {
return mutableList.count()
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tv = holder.containerView as TextView
tv.text = mutableList[position].data

}


}

The code for the abstract Dynamic Search Adapter is below.

abstract class DynamicSearchAdapter<T : DynamicSearchAdapter.Searchable>(private val searchableList: MutableList<T>) :
RecyclerView.Adapter<ViewHolder>(), Filterable {

// Single not-to-be-modified copy of original data in the list.
private val originalList = ArrayList(searchableList)
// a method-body to invoke when search returns nothing. It can be null.
private var onNothingFound: (() -> Unit)? = null

/**
* Searches a specific item in the list and updates adapter.
* if the search returns empty then onNothingFound callback is invoked if provided which can be used to update UI
*
@param s the search query or text. It can be null.
*
@param onNothingFound a method-body to invoke when search returns nothing. It can be null.
*/
fun search(s: String?, onNothingFound: (() -> Unit)?) {
this.onNothingFound = onNothingFound
filter.filter(s)

}

override fun getFilter(): Filter {
return object : Filter() {
private val filterResults = FilterResults()
override fun performFiltering(constraint: CharSequence?): FilterResults {
searchableList.clear()
if (constraint.isNullOrBlank()) {
searchableList.addAll(originalList)
} else {
val searchResults = originalList.filter { it.getSearchCriteria().contains(constraint) }
searchableList
.addAll(searchResults)
}
return filterResults.also {
it
.values = searchableList
}
}

override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
// no need to use "results" filtered list provided by this method.
if (searchableList.isNullOrEmpty())
onNothingFound?.invoke()
notifyDataSetChanged()

}
}
}

interface Searchable {
/** This method will allow to specify a search string to compare against
your search this can be anything depending on your use case.
*/
fun getSearchCriteria(): String
}


}

All the types of list you have must implement the Searchable interface listed in the above adapter class in order to be made searchable. See examples below for a type of list SearchModel1 and SearchModel2.

class SearchModel1(val data: String) : DynamicSearchAdapter.Searchable {


override fun getSearchCriteria(): String {
return data
}
}
class SearchModel2(val data: String) : DynamicSearchAdapter.Searchable {


override fun getSearchCriteria(): String {
return data
}
}

Your lists would look like List<SearchModel1> and List<SearchModel2> and you will also need to return a search query to be compared against in each class method getSearchCriteria. This method is used in filtering out data for each individual listing. You can make as many types of lists you want as long as they fulfill the above mentioned requirements. The search method can be used to trigger searches on the list.

The search method works asynchronously by Android’s Filterable Interface and uses linear search with two parameters.

String: A search query.

(()->Unit)?: A lambda body to invoke when nothing matching the search was found. It can be used to update the UI or null.

An example of a sample activity will look like below.

The user enters data in a search view. It shows a toast if nothing matching the input is found in the list.

class SampleActivity2 : AppCompatActivity(), SearchView.OnQueryTextListener {


private lateinit var searchAdapter3: SearchAdapter3

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_main)
rv.layoutManager = LinearLayoutManager(this)
searchAdapter3 = SearchAdapter3(mutableListOf<SearchModel3>().populateWithUUIDSM3())
rv.adapter = searchAdapter3
searchV.setOnQueryTextListener(this)

}

override fun onQueryTextChange(newText: String?): Boolean {
search(newText)
return true
}

override fun onQueryTextSubmit(query: String?): Boolean {
search(query)
return true
}

private fun search(s: String?) {
searchAdapter3.search(s) {
// update UI on nothing found
Toast.makeText(this, "Nothing Found", Toast.LENGTH_SHORT).show()
}
}

How to run ?

You can find the project on Github. It contains an activity and 3 fragments as examples which all display a searchable listing using a recycler view and have adapters extending the Dynamic Search Adapter. You can clone the project in Android Studio to run it.

Last Step

Many thanks to everyone who reads this and even more to those who used this. Let me know if you like this and whatever topics you wish for me to write in regards to Android Development. You can follow me here for updates and/or reach me at LinkedIn or Facebook. You can greatly help by starring and forking on github.

--

--

Mobin Munir
AndroidPub

Lead Software Engineer (Android) @ Yassir || Fitness Freak of 13 years