Android RecyclerView Filter Using Filterable

There might be requirement in your application where you want to implement the search functionality by having SearchView widget inside the toolbar / actionBar for filtering the items of recyclerview . In the previous series of tutorial we have seen the basics of a RecyclerView  . 

In this tutorial we will see the logic for filtering the items in  RecyclerView Using filterable interface .




1. Build Gradle


Add the following lines to the dependencies section in your project's build.grade file and sync . It includes design support library , recyclerView library .

file : build.gradle
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}


2. Menu


Create menu XML layout inside res/menu/ and add SearchView widget as  menu item

file : menu_main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_search"
        android:icon="@drawable/ic_search_24"
        android:title="@string/app_name"
        app:actionViewClass="android.widget.SearchView"
        app:showAsAction="ifRoom|collapseActionView" />

</menu>

3. XML Layout


Create XML Layout for MainActivity and add recyclerView widget to it .

file : activity_main.xml
<?xml version="1.0" encoding="utf-8"?>

<androidx.recyclerview.widget.RecyclerView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="10dp" />

Create  XML Layout file in res/layout and name it row_item.xml , This layout defines the layout for Items of RecyclerView . Here In this example we will have one TextView  .

file : row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/txt"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="8dp"
    android:paddingTop="16dp"
    android:paddingRight="8dp"
    android:paddingBottom="16dp"
    android:textSize="20sp" />

4. Data


To Load Data Into RecyclerView , we will use Locale class to get full list of country and we will also create emoji using country iso code .

file : Model.kt
package com.tutorialsbuzz.recyclerviewfilter

data class Model(val name: String) {}

5. Adapter ViewHolder Filterable


5.1  Adapter

Create a Adapter That RecyclerView Can Use ,Create a class CustomAdapter extend it to RecyclerView.Adapter . 

RecyclerView.Adapter has three abstract methods that we must override .
  • onCreateViewHolder() :Inside this method we specify the layout that each item of the RecyclerView should use .onCreateViewHolder has return type of RecyclerView.ViewHolder which represent each row of recyclerView. Using Inflator get the view of above defined row_item and pass it to viewholder constructor and then return.
  • onBindViewHolder() : Inside this method data will be displayed at the specified position .
  • getItemCount() : Returns the total number of items in the data set held by the adapter.

5.2 ViewHolder

Create Inner class ViewHolder inside CustomAdapter class extend it to RecyclerView.ViewHolder , add bind function which takes above defined model data class object .

5.3 Filterable 

Implement Filterable interface to CustomAdapter and override and getFilter() , Inside this method create an instance of filter and override the performFiltering and publishResults function 
  • performFiltering : This function takes constraint as parameter , list is filtered based on constraint the list is filtered , which is then set to FilterResults and then return FilterResults instance .
  • publishResults : This function  it takes FilterResults as parameter which is returned from performFiltering function , Inside this we will set filtered list to adapter and call notifyDataSetChanged .

file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerviewfilter

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 androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row_item.view.*
import kotlin.collections.ArrayList

class CustomAdapter(var countryList: MutableList<Model>, val context: Context) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {

    var countryFilterList = ArrayList<Model>()
    // exampleListFull . exampleList

    init {
        countryFilterList = countryList as ArrayList<Model>
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as ViewHolder).bind(countryFilterList.get(position));
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        return ViewHolder(layoutInflater.inflate(R.layout.row_item, parent, false))
    }

    override fun getItemCount(): Int {
        return countryFilterList.size
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bind(model: Model): Unit {
            itemView.txt.text = model.name
        }
    }

    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(constraint: CharSequence?): FilterResults {
                val charSearch = constraint.toString()
                if (charSearch.isEmpty()) {
                    countryFilterList = countryList as ArrayList<Model>
                } else {
                    val resultList = ArrayList<Model>()
                    for (row in countryList) {
                        if (row.name.toLowerCase().contains(constraint.toString().toLowerCase())) {
                            resultList.add(row)
                        }
                    }
                    countryFilterList = resultList
                }
                val filterResults = FilterResults()
                filterResults.values = countryFilterList
                return filterResults
            }

            override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
                countryFilterList = results?.values as ArrayList<Model>
                notifyDataSetChanged()
            }
        }
    }

}

6. MainActivity


  • Create a koltin class MainActivity.kt and extend it to AppCompatActivity class .
  • Override onCreate function and set the content of this MainActivity with above defined xml layout (activity_main.xml). 
  • Create full list of country using Locale class ,this list will be set to recyclerView.
  • Set Vertical Linear LayoutManager to RecyclerView.
  • Create Instance of Adapter and set to RecyclerView .
SearchView Widget :
  • Get the reference of menu item and call getView and then type cast it androidx.appcompat.widget.SearchView.
  • On SearchView instance call setOnQueryTextListener and override onQueryTextSubmit and onQueryTextChange.
  • Inside the onQueryTextChange function get the instance of filter using adapter and call filter function by passing query string .


file : MainActivity.kt
package com.tutorialsbuzz.recyclerviewfilter

import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.SearchView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*

class MainActivity : AppCompatActivity() {

    var adapter: CustomAdapter? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        adapter = CustomAdapter(getCountries(), this)

        recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
        recyclerView.addItemDecoration(SimpleDividerItemDecoration(this))
        recyclerView.adapter = adapter;
    }

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

        val item = menu?.findItem(R.id.action_search);
        val searchView = item?.actionView as SearchView

        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(p0: String?): Boolean {
                return true
            }

            override fun onQueryTextChange(query: String?): Boolean {
                Log.d("onQueryTextChange", "query: " + query)
                adapter?.filter?.filter(query)
                return true
            }
        })

        item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
            override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
                adapter?.filter?.filter("")
                showToast("Action Collapse")
                return true
            }

            override fun onMenuItemActionExpand(p0: MenuItem?): Boolean {
                showToast("Action Expand")
                return true
            }
        })

        return super.onCreateOptionsMenu(menu)
    }


    private fun getCountries(): MutableList<Model> {
        val mdList = mutableListOf<Model>()
        for (countryISO in Locale.getISOCountries()) {
            val locale = Locale("", countryISO)
            if (!locale.displayCountry.isEmpty()) {
                mdList.add(Model(locale.displayCountry + "  " + counrtyFlag(countryISO)))
            }
        }
        return mdList
    }


    private fun counrtyFlag(countryCode: String): String {
        val flagOffset = 0x1F1E6
        val asciiOffset = 0x41
        val firstChar = Character.codePointAt(countryCode, 0) - asciiOffset + flagOffset
        val secondChar = Character.codePointAt(countryCode, 1) - asciiOffset + flagOffset
        val flag =
            (String(Character.toChars(firstChar)) + String(Character.toChars(secondChar)))

        return flag
    }

    private fun showToast(msg: String) {
        Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
    }

}




RecyclerView Related
  1. RecyclerView
  2. RecyclerView Item Click Ripple Effect
  3. RecyclerView With Item Divider
  4. RecyclerView With CardView
  5. RecyclerView GridLayout
  6. RecyclerView StaggeredGrid Layout
  7. RecyclerView Swipe To Delete
  8. Android RecyclerView Swipe to Delete Undo
  9. Android RecyclerView Interactive Swipe Like Gmail
  10. Android RecyclerView Buttons Under Swipe
  11. Android RecyclerView MultiViewType
  12. Android RecyclerView Item Drag and Drop
  13. Android RecyclerView GridLayout Drag and Drop
  14. Android Filter RecyclerView Using SearchView In Java
  15. Android RecyclerView Filter Animation

2 comments:

Unknown said...

thank you Pavan Ji,
For This Tutorial, it cleared my two days of Struggle

reddit blog said...

Good

Post a Comment