Android RecyclerView With SearchView

This article explains how to integrate searchview in toolbar to search data in recyclerview. We can use Filterable interface to sort data using some conditions.

We need to implement Filterable interface in adapter class. For this we must have to override getFilter method to sort data. In this method, We provide some conditions. So we can get specific data on these conditions.

In this example, First we fetch employees data from api. Then we filter data to search specific employees by name or username of employees.

 

Add below dependencies in your app level build.gradle file

We are using retrofit to fetch employees data from api. To display data, We need to add recyclerview and cardview libraries.

implementation ‘com.squareup.retrofit2:retrofit:2.5.0’
implementation ‘com.squareup.retrofit2:converter-gson:2.5.0’
implementation ‘com.squareup.okhttp3:logging-interceptor:3.9.1’
implementation ‘androidx.recyclerview:recyclerview:1.1.0’
implementation ‘androidx.cardview:cardview:1.0.0’
 
 

Add SearchView In Toolbar

 

Create Menu File to display SearchMenu in Toolbar

<menu 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”
tools:context=”info.androidhive.recyclerviewsearch.MainActivity”>
<item
android:id=”@+id/action_search”
android:icon=”@android:drawable/ic_search_category_default”
android:title=”Search”
app:showAsAction=”always”
app:actionViewClass=”androidx.appcompat.widget.SearchView” />
</menu>
 
 

Create xml file under res->xml folder

<?xml version=”1.0″ encoding=”utf-8″?>
<searchable xmlns:android=”http://schemas.android.com/apk/res/android”
android:hint=”search”
android:label=”@string/app_name” />
 
 

Changes in related activity in manifest file

<meta-data
android:name=”android.app.searchable”
android:resource=”@xml/searchable” />
<intent-filter>
<action android:name=”android.intent.action.SEARCH” />
</intent-filter>
 
 

Add Menu in Activity Toolbar

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.menu_main, menu)

    val mSearchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
    mSearchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
    mSearchView?.setSearchableInfo(mSearchManager.getSearchableInfo(componentName))
    mSearchView?.maxWidth = Int.MAX_VALUE

    mSearchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            employeesAdapter?.filter?.filter(query)
            return false
        }

        override fun onQueryTextChange(query: String?): Boolean {
            employeesAdapter?.filter?.filter(query)
            return false
        }
    })
    return true
}

 
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    val id = item?.itemId

    return if (id == R.id.action_search) {
        true
    } else super.onOptionsItemSelected(item)

}

 
 

Filter RecyclerView Data

override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
    if (charSequence.toString().isEmpty()) {
    filteredListEmployees = listEmployees
    } else {
        val employeesFilteredList: MutableList<Employee> = ArrayList()
        listEmployees?.let {
            for (employee in it) {
                employee.name?.let { mName ->
                employee.userName?.let { mUserName ->
                    if (mName.toLowerCase().contains(charSequence.toString().toLowerCase())
                    || mUserName.toLowerCase().contains(charSequence.toString().toLowerCase()) ) {
                        employeesFilteredList.add(employee)
                    }
                }
                }
            }
        }
         filteredListEmployees = employeesFilteredList
    }
    val filterResults = FilterResults()
    filterResults.values = filteredListEmployees
    return filterResults
}

override fun publishResults( charSequence: CharSequence, filterResults: FilterResults ) {
    filteredListEmployees = filterResults.values as MutableList<Employee>
    notifyDataSetChanged()
}
}
}

 
 

Final Code

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.recyclerviewwithsearchfiltersample">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_main"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
 

employee_row.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="10dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:layout_marginBottom="8dp"
    android:layout_marginStart="15dp"
    android:layout_marginEnd="15dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp">

        <TextView
            android:id="@+id/txt_employee_name"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="22sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_employee_info1"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/txt_employee_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/txt_employee_info2"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/txt_employee_info1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/txt_employee_address"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/txt_employee_info2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>
 

Employee.kt

package com.example.recyclerviewwithsearchfiltersample.models

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class Employee(
    @SerializedName("id")
    val id: Int? = null,

    @SerializedName("name")
    val name: String? = null,

    @SerializedName("username")
    val userName: String? = null,

    @SerializedName("email")
    val email: String? = null,

    @SerializedName("address")
    val employeeAddress : EmployeeAddress? = null,

    @SerializedName("phone")
    val phone: String? = null,

    @SerializedName("website")
    val website: String? = null
) : Serializable
 

EmployeeAddress.kt

package com.example.recyclerviewwithsearchfiltersample.models

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class EmployeeAddress(

    @SerializedName("street")
    val street: String? = null,

    @SerializedName("suite")
    val suite: String? = null,

    @SerializedName("city")
    val city: String? = null,

    @SerializedName("zipcode")
    val zipCode: String? = null

) : Serializable
 

ApiClient.kt

package com.example.recyclerviewwithsearchfiltersample.utils

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ApiClient {

    private const val BASE_URL: String = "https://jsonplaceholder.typicode.com/"

    private val gson : Gson by lazy {
        GsonBuilder().setLenient().create()
    }

    private val interceptor : HttpLoggingInterceptor by lazy {
        HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
    }

    private val httpClient : OkHttpClient by lazy {
        OkHttpClient.Builder().addInterceptor(interceptor).build()
    }

    private val retrofit : Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(httpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
    }

    val apiService :  ApiService by lazy{
        retrofit.create(ApiService::class.java)
    }

}
 

ApiService.kt

package com.example.recyclerviewwithsearchfiltersample.utils

import com.example.recyclerviewwithsearchfiltersample.models.Employee
import retrofit2.Call
import retrofit2.http.GET

interface ApiService {

    @GET("users")
    fun getUsers(): Call<MutableList<Employee>>

}
 

Utility.kt

package com.example.recyclerviewwithsearchfiltersample.utils

import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.net.ConnectivityManager
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import com.example.recyclerviewwithsearchfiltersample.R

object Utility {

    private var progressBar: ProgressBar? = null

    fun Context.isInternetAvailable(): Boolean {
        try {
            val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val netInfo = cm.activeNetworkInfo
            return if (netInfo != null &amp;&amp; netInfo.isConnected) {
                true
            } else {
                showErrorToast("Internet not available. Please try again!!")
                false
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    fun Context.showErrorToast(message: String?) {

        try {
            val toast = Toast.makeText(this, message, Toast.LENGTH_LONG)

            val textView = toast.view.findViewById(android.R.id.message) as? TextView
            textView?.setTextColor(Color.WHITE)
            textView?.gravity = Gravity.CENTER

            toast.view.setBackgroundColor(Color.RED)

            toast.setGravity(Gravity.TOP or Gravity.FILL_HORIZONTAL, 0, 0)

            toast.show()
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    fun Context.showProgressBar() {
        try {
            val layout = (this as? Activity)?.findViewById<View>(android.R.id.content)?.rootView as? ViewGroup

            progressBar = ProgressBar(this, null, android.R.attr.progressBarStyleLarge)
            progressBar?.let { it1 ->
                it1.isIndeterminate = true

                val params = RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.MATCH_PARENT,
                    RelativeLayout.LayoutParams.MATCH_PARENT
                )

                val rl = RelativeLayout(this)

                rl.gravity = Gravity.CENTER
                rl.addView(it1)

                layout?.addView(rl, params)

                it1.visibility = View.VISIBLE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun hideProgressBar() {
        try {
            progressBar?.let {
                it.visibility = View.GONE
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

}
 

EmployeesAdapter.kt

<pre class="wp-block-syntaxhighlighter-code">
package com.example.recyclerviewwithsearchfiltersample.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerviewwithsearchfiltersample.R
import com.example.recyclerviewwithsearchfiltersample.models.Employee


class EmployeesAdapter(private val context: Context, private var listEmployees: MutableList<Employee>?) : RecyclerView.Adapter<EmployeesAdapter.MyViewHolder>(), Filterable {

    private var filteredListEmployees : MutableList<Employee>? = null

    init {
        this.filteredListEmployees = listEmployees
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(context)
        val view: View = inflater.inflate(R.layout.employee_row,parent,false)
        return MyViewHolder(view)
    }

    override fun getItemCount(): Int {
        filteredListEmployees?.let {
            return it.size
        }
        return 0
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val employee = filteredListEmployees?.get(position)
        holder.txtEmployeeName?.text = employee?.name
        holder.txtEmployeeInfo1?.text = employee?.userName + " | " + employee?.email
        holder.txtEmployeeInfo2?.text = employee?.phone + " | " + employee?.website

        val employeeAddress = employee?.employeeAddress
        holder.txtEmployeeAddress?.text = employeeAddress?.suite + "," + employeeAddress?.street + "," + employeeAddress?.city + "," + employeeAddress?.zipCode
    }

    class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view){

        var txtEmployeeName: TextView? = null
        var txtEmployeeInfo1: TextView? = null
        var txtEmployeeInfo2: TextView? = null
        var txtEmployeeAddress: TextView? = null

        init {
            txtEmployeeName = view.findViewById(R.id.txt_employee_name)
            txtEmployeeInfo1 = view.findViewById(R.id.txt_employee_info1)
            txtEmployeeInfo2 = view.findViewById(R.id.txt_employee_info2)
            txtEmployeeAddress = view.findViewById(R.id.txt_employee_address)
        }

    }

    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(charSequence: CharSequence): FilterResults {
                if (charSequence.toString().isEmpty()) {
                    filteredListEmployees = listEmployees
                } else {
                    val employeesFilteredList: MutableList<Employee> = ArrayList()
                    listEmployees?.let {
                        for (employee in it) {
                            employee.name?.let { mName ->
                                employee.userName?.let { mUserName ->
                                    if (mName.toLowerCase().contains(charSequence.toString().toLowerCase())
                                        || mUserName.toLowerCase().contains(charSequence.toString().toLowerCase()) ) {
                                        employeesFilteredList.add(employee)
                                    }
                                }
                            }
                        }
                    }
                    filteredListEmployees = employeesFilteredList
                }
                val filterResults = FilterResults()
                filterResults.values = filteredListEmployees
                return filterResults
            }

            override fun publishResults(
                charSequence: CharSequence,
                filterResults: FilterResults
            ) {
                filteredListEmployees = filterResults.values as MutableList<Employee>
                notifyDataSetChanged()
            }
        }
    }

}</pre>
 

MainActivity.kt

package com.example.recyclerviewwithsearchfiltersample

import android.app.SearchManager
import android.content.Context
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recyclerviewwithsearchfiltersample.adapter.EmployeesAdapter
import com.example.recyclerviewwithsearchfiltersample.models.Employee
import com.example.recyclerviewwithsearchfiltersample.utils.ApiClient
import com.example.recyclerviewwithsearchfiltersample.utils.Utility.hideProgressBar
import com.example.recyclerviewwithsearchfiltersample.utils.Utility.isInternetAvailable
import com.example.recyclerviewwithsearchfiltersample.utils.Utility.showProgressBar
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response


class MainActivity : AppCompatActivity() {

    private var listEmployees: MutableList<Employee> = mutableListOf<Employee>()
    private var employeesAdapter: EmployeesAdapter? = null
    private var mSearchView: SearchView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        listEmployees = mutableListOf()

        recycler_main.layoutManager = LinearLayoutManager(this@MainActivity)
        employeesAdapter = EmployeesAdapter(
            this,
            listEmployees
        )
        recycler_main.adapter = employeesAdapter

        if (isInternetAvailable()) {
            getEmployeesData()
        }

    }

    private fun getEmployeesData() {

        showProgressBar()

        ApiClient.apiService.getUsers().enqueue(object : Callback<MutableList<Employee>> {
            override fun onFailure(call: Call<MutableList<Employee>>, t: Throwable) {
                hideProgressBar()
            }

            override fun onResponse(
                call: Call<MutableList<Employee>>,
                response: Response<MutableList<Employee>>
            ) {
                hideProgressBar()
                val employeesResponse = response.body()
                listEmployees.clear()
                employeesResponse?.let { listEmployees.addAll(it) }
                employeesAdapter?.notifyDataSetChanged()
            }

        })

    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)

        val mSearchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        mSearchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
        mSearchView?.setSearchableInfo(mSearchManager.getSearchableInfo(componentName))
        mSearchView?.maxWidth = Int.MAX_VALUE

        mSearchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String?): Boolean {
                employeesAdapter?.filter?.filter(query)
                return false
            }

            override fun onQueryTextChange(query: String?): Boolean {
                employeesAdapter?.filter?.filter(query)
                return false
            }
        })
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        val id = item?.itemId

        return if (id == R.id.action_search) {
            true
        } else super.onOptionsItemSelected(item)

    }

}
 
 
recyclerview
 
 
 
 

Leave a Reply