How to add Search in RecyclerView using Kotlin

How to add Search in RecyclerView using Kotlin

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>

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
    }
  
    // ...

}

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
    }
  
    // ...
}

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]
        
        // ...
    }
}

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 {
      
    }

}

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?) {
                
            }
          
        }
    }

}

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()
            }
          
        }
    }

}

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
            }

        })
      
        // ...
      
    }
  
}

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)

        // ...

    }

}

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)

        // ...

    }

}

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

        // ...

    }

}
You can find the final project here

If you have any questions feel free to DM me on Twitter @johncodeos or leave a comment below!

4
Leave a Reply

avatar
1 Comment threads
3 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
John Codeosakash Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
akash
Guest
akash

charSearch not defined?

akash
Guest
akash

and also adapter.filter.filter?

Follow me