Today, I will show you how to search for items in a RecyclerView using SearchView.
In this example, we have a RecyclerView already set up, and the only thing we do is to add the searching functionality.
We have a list of countries as sample data and using the search field we get the country we want faster.
Adding SearchView in RecyclerView
Add the SearchView in your layout, inside a LinearLayout with a vertical orientation.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/country_content_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.appcompat.widget.SearchView
android:id="@+id/country_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:textCursorDrawable="@null"
app:iconifiedByDefault="false"
app:queryBackground="@null" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/country_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark" />
</LinearLayout>
Code language: HTML, XML (xml)
In the RecyclerView adapter (In this example, RecyclerView_Adapter.kt), create an ArrayList with a name countryFilterList, and pass all the items..
class RecyclerView_Adapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var countryFilterList = ArrayList<String>()
init {
countryFilterList = countryList
}
// ...
}
Code language: Kotlin (kotlin)
Return the size of countryFilterList so every time will return the right amount of items that match the characters you typing in the SearchView.
class RecyclerView_Adapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// ...
override fun getItemCount(): Int {
return countryFilterList.size
}
// ...
}
Code language: Kotlin (kotlin)
Now, in the onBindViewHolder get the item for each row from the countryFilterList array.
class RecyclerView_Adapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// ...
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// ...
holder.itemView.select_country_text.text = countryFilterList[position]
// ...
}
}
Code language: Kotlin (kotlin)
Add the Filterable class in the RecyclerView_Adapter and the method getFilter()
class RecyclerView_Adapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
override fun getFilter(): Filter {
}
}
Code language: Kotlin (kotlin)
Inside the getFilter() method, return a Filter() object:
class RecyclerView_Adapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
}
}
}
}
Code language: Kotlin (kotlin)
The performFiltering method checks if we have typed a text in the SeachView.
If there is not any text, will return all items.
If there is a text, then we check if the characters match the items from the list and return the results in a FilterResults type.
The publishResults get these results, passes it to the countryFilterList array and updates the RecyclerView.
class RecyclerView_Adapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charSearch = constraint.toString()
if (charSearch.isEmpty()) {
countryFilterList = countryList
} else {
val resultList = ArrayList<String>()
for (row in countryList) {
if (row.toLowerCase(Locale.ROOT).contains(charSearch.toLowerCase(Locale.ROOT))) {
resultList.add(row)
}
}
countryFilterList = resultList
}
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()
}
}
}
}
Code language: Kotlin (kotlin)
Go back to the Activity file (MainActivity.kt) and add the setOnQueryTextListener.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ...
country_search.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
adapter.filter.filter(newText)
return false
}
})
// ...
}
}
Code language: Kotlin (kotlin)
The onQueryTextChange method called every time we typing on the SearchView and updates the RecyclerView with the new results.
Customizing the SearchView
If you want to change the color of the search icon (magnifying glass):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ...
val searchIcon = country_search.findViewById<ImageView>(R.id.search_mag_icon)
searchIcon.setColorFilter(Color.WHITE)
// ...
}
}
Code language: Kotlin (kotlin)
For the color of the cancel button:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ...
val cancelIcon = country_search.findViewById<ImageView>(R.id.search_close_btn)
cancelIcon.setColorFilter(Color.WHITE)
// ...
}
}
Code language: Kotlin (kotlin)
And to change the text color of the TextView:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ...
val textView = country_search.findViewById<TextView>(R.id.search_src_text)
textView.setTextColor(Color.WHITE)
// If you want to change the color of the cursor, change the 'colorAccent' in colors.xml
// ...
}
}
Code language: Kotlin (kotlin)
You can find the final project here
If you have any questions, please feel free to leave a comment below
charSearch not defined?
I fixed it! Thanks for noticing it!
and also adapter.filter.filter?
Yeah, it’s the same like the following:
You set a new filter to the current filter
I can not get the adapter.filter.filter(newText) to work, would you be able to assist. Its highlighted in red and calls it an unsolved reference. I tried the workaround you posted in the comments and that also presents the same issue. The middle filter is a problem. I am using this in conjunction with an api connection to get the country names, but i do not see how it would present such an issue.
The first filter from the adapter.filter.filter(newText) comes from the getFilter() in the RecyclerView_Adapter, read the tutorial again (the RecyclerView_Adapter part) and see if you added the Filterable class in the RecyclerView_Adapter
The (constraint) in the “performFiltering” method is not recognized ..
Did you add the Filterable class in the RecyclerView_Adapter?
Yes, I did.
Sorry, the problem was in the parameter’s name which was “p0” not “constraint”.
Thank you.
Thank you very much
I got this problem
lateinit property adapter has not been initialized
How to fix it ?You haven’t initialized the adapter. Check out the demo app to see what is missing.
HI, i cannot use toLowerCase() on row, do you have any idea why? toLowerCase() gives unresolved reference and “type mismatch”. I’m stuck on this part, please, help
Check if your row is a String
I used toString before the toLowerCase() and it looks like it worked out. Thank you.
Looks like i can’t use resultList.add(row) if my class is Array(Class) and not a ArrayList(Class) 🙁
Try to use ArrayList instead of Array
If i use ArrayList, List or MutableList, my Gson crashs telling me that it expected an object and got an array. Strangely, if i use an Array<MyClass>, it works and shows my data on the RecyclerView.
Something is wrong when you are parsing JSON. I have seen this error (expected an object and got an array) multiple times in the past.
Try this:
Then, you can use myList the same way I use ArrayList in the article
what if i use listOf<ToDo> ?
You mean, instead of
List<MyClass>
to uselistOf<MyClass>
?listOf<MyClass> . please explain to me i’m new to kotlin 🙂
Do you have problem with
.toLowerCase()
too?You need to have an ArrayList of String to be able to use it. What type of Array do you use?
No, not the
.toLowerCase()
, but on theresultList.add(row)
. Apparently i can’t use it too if i’m usinglistOf
.What is
ToDo
in yourlistOf
?Is it a data class?
If you don’t know what I’m taking about, I mean is it
ToDo
looks like this?Yes, it is a data class. it kinda looks something like that. it’s a data class that carry Entity for my room database. I’m sorry if i can’t explain what i mean very well but I hope you understand. In my adapter class, i’m using
listOf<ToDo>
No, it’s ok, now I understand. You can do it like that:
I’m using as an example the data class ToDo from my previous comment.
So, when you search, you will search for the goal(The text you type as a goal that you want to achieve in a ToDo app)
Thank you so much. But one last question, what about the MainActivity? I mean i’m using room database, so it search for the things i want it to seach (for example the goal that you’ve just said). How should i write the function that do the filter? Because I’m seeing that you are not using room as i do so it must be kinda different than what you do in your MainActivity
No, you don’t need to do any changes on the MainActivity, just add the setOnQueryTextListener as I say on the article.
my item gets double
Maybe you add your items twice in the ArrayList
no i am checking the list is correct. after that i tries with removeallviewinlayout this is working for one to two search then again some unrelated data get inflate.
Did you check the demo app with your data to see if it’s happening the same thing?
Let me try this
Where does the adapter in adapter.filter.filter(newText) comes from?
It’s highlighted in red and asks to create a variable adapter.
In my tutorial, I suppose you have initialized already the adapter before.
Check the demo app to see what I’m talking about.
exception is being thrown for
I have already tried this out and it worked. But now I am trying to replicate it but it’s throwing this exception. Any idea? If I comment this method, it doesn’t crash but then I won’t be able to use the search feature!
PS, I’m using this along with a PageAdapter, so I have 2 recycler view loading when I get into the fragment. Would that be a problem? Should I implement this for both the recycler views? I’ll give that a try in the meantime.
It’s showing an error in “FilterResults()”
What kind of error? Did you check the demo app?
Hi,
I finish practice. Thank you. I have one question.
We listed all country and searchable. But I need when I click one of them open new page.
For example I am searching United Kingdom then I click and open introduction page united kingdom it was possible ?
Add a setOnClickListener on the onBindViewHolder, like that:
I’m getting the following error for my code:
Type mismatch.
Required: SearchView.OnQueryTextListener!
Found:
Code:
The error is shown for “object: SearchView.OnQueryTextListener”
Try to clean your project and re-build again.
If the problem still exists after that, try the demo app and see what is missing in your app.
can add a new item ether then country, for example, product and display products page with information
Also can make the list float?
Yeah, you can put whatever you like.
What do you mean by “make the list float”?
I mean make list like this picture
You mean I can add any information to the page of the item?
Is this a RecyclerView?(Probably is) Then yeah, you can!
Yes, you can add any information you want. The code that you’ll need to filter the results is the same.
No, this appears when I press to the icon-search and appear the item.
Do you have any external contact information?
You can send me through blog’s contact form
which file used to change the items?
There’s no file. I use the following method to get the countries and add them to the countryListWithEmojis ArrayList.
So, How Can I change the Item?
I’m sorry for bothering you but I am the beginner to the android
I am in university also I am in final level I need your help to gruduate
please could give me any feedback about your contact information
like a phone number or email
because I want to see the project documentation to estimate the can be successful or not.
I know you are bussy but just this time ….
I try to implemnt the project idea over 10 thousend time
But I dont get any benifie result …
how I can change this method to accept my Item like for example katkit chocolate