Android Filter RecyclerView Using SearchView In Java

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 .





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 'com.google.android.material:material:1.2.1'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

2. Menu

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

file : menu.xml
<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="@android:drawable/ic_menu_search"
        android:title="@string/action_search"
        app:actionViewClass="androidx.appcompat.widget.SearchView"
        app:showAsAction="always|collapseActionView" />

</menu>

In this tutorial we will create RecyclerView in one of Tabs of TabLayout , Creating TabLayout is outside the scope of this tutorial , In the previous tutorial I have covered how to create TabLayout you can refer for it .

3. XML Layout

Create  XML layout file in res/layout and name it tab_one_fragment.xml and Add RecyclerView inside RelativeLayout.

file : tab_one_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:background="@color/fragment_bg">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Create  XML Layout file in res/layout and name it list_row.xml , This Layout defines the layout for Items of RecyclerView . Here In this example we will add two TextView inside LinearLayout .

file : list_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="@drawable/selector_row"
    android:clickable="true"
    android:focusableInTouchMode="true"
    android:focusable="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/country_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Name"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/country_iso"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ISo"
        android:textSize="16sp"/>
    
</LinearLayout>

4. Java Model

Create a Bean Class CountryModel which defines the data for items of recyclerview .
file : CountryModel.java

package com.tutorialsbuzz.recyclerview.TabFragments;

public class CountryModel {

    String name;
    String isocode;

    CountryModel(String name, String isocode){
        this.name=name;
        this.isocode=isocode;
    }

    public String getName() {
        return name;
    }

    public String getisoCode() {
        return isocode;
    }
}

5. Adapter

  • Create A Class RVAdapter and this class Must Extend RecyclerView.Adapter. This adapter follows the view holder design pattern, which means that you have to define a custom class that extends RecyclerView.ViewHolder  (This pattern minimizes the number of calls to the costly findViewById method.) .

  • Add a constructor to the custom adapter so that it has a handle to the data that the RecyclerView displays , As our data is in the form of a List<String> .

  • RecyclerView.Adapter has three abstract methods that we must override .

    1. getItemCount() :  This method return the number of items present in the data

    2. onCreateViewHolder() : Inside this method we specify the layout that each item of the RecyclerView should use. This is done by inflating the layout using LayoutInflater, passing the output to the constructor of the custom ViewHolder.

    3. onBindViewHolder() : This Method This method is very similar to the getView method of a ListView's adapter. In our example, here's where you have to set the String values to TextView.

  • I have created a method setFilter , we call this method inside onQueryTextChange  method of SearchView.onQueryTextListener interface by passing filtered data list and inside this method make a call to notifyDataSetChanged() api which will do the filtering magic .

file : RVAdapter.java

package com.tutorialsbuzz.recyclerview.TabFragments;

import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.tutorialsbuzz.recyclerview.R;
import java.util.ArrayList;
import java.util.List;

public class RVAdapter extends RecyclerView.Adapter<ItemViewHolder> {

    private List<CountryModel> mCountryModel;
    private List<CountryModel> mOriginalCountryModel;

    public RVAdapter(List<CountryModel> mCountryModel) {
        this.mCountryModel = mCountryModel;
        this.mOriginalCountryModel = mCountryModel;
    }

    @Override
    public void onBindViewHolder(ItemViewHolder itemViewHolder, int i) {
        final CountryModel model = mCountryModel.get(i);
        itemViewHolder.bind(model);
    }

    @Override
    public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, viewGroup, false);
        return new ItemViewHolder(view);
    }

    @Override
    public int getItemCount() {
        return mCountryModel.size();
    }

    public void setFilter(List<CountryModel> countryModels){
        mCountryModel = new ArrayList<>();
        mCountryModel.addAll(countryModels);
        notifyDataSetChanged();
    }

}

6. View Holder

  • ItemViewHolder class and this class Must Extend  RecyclerView.ViewHolder

  • We already defined the XML layout list_row.xml , We are going to reuse that layout now. Inside the constructor of our custom ViewHolder, initialize the views that belong to the items of our RecyclerView.

  • I have created method bind , we this method this method inside onBindViewHolder() by passing country data model class object.
file : ItemViewHolder.java

package com.tutorialsbuzz.recyclerview.TabFragments;

import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import com.tutorialsbuzz.recyclerview.R;

public class ItemViewHolder extends RecyclerView.ViewHolder {

    public TextView name_TextView;
    public TextView iso_TextView;

    public ItemViewHolder(View itemView) {
        super(itemView);
        itemView.setClickable(true);
        name_TextView = (TextView) itemView.findViewById(R.id.country_name);
        iso_TextView = (TextView) itemView.findViewById(R.id.country_iso);
    }

    public void bind(CountryModel countryModel) {
        name_TextView.setText(countryModel.getName());
        iso_TextView.setText(countryModel.getisoCode());
    }

}


6. Fragment

  • Create a TabOneFragment which extends Fragment .

  • Inside the onCreateView method get the reference to recyclerview and set the LayoutManager as LayoutLayout.

  • Inside the OnActivityCreate method call to setAdpter on recyclerview instance and pass the instance of above define RVAdapter as argument.

7. Filtering Logic
  • Inside the onCreateOptionMenu get the reference of searchView widget and set onQueryTextListener on it .

  • Upon setting onQueryTextListener on SearchView override the onQueryTextChange and onQueryTextSubmit method.

  • I have Created a method filter which takes list of CountryModel and query string as parameter , inside this method we will filter the CountryModel list on one of the constraints of CountryModel (here in this example i am filter the list on name constrain , if you want you can filter on isocode ) and return the filtered list .

  • Inside the onQueryTextChange we make a call to filter method and get the return filtered list this which in-turn passed as a argument while make call to setFilter of RVAdapter .

Note : When you perform filter and then Switch between tabs in TabLayout In such scenario the recyclerview won't return the original set of data  so to overcome this you have set the onActionExapndListener on MenuItem and then inside the onMenuItemActionCollapse make a call to setFilter method on adapter instance by passing the original list as argument . 


file : TabOneFragment.java

package com.tutorialsbuzz.recyclerview.TabFragments;

import android.os.Bundle;

import androidx.fragment.app.Fragment;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.SearchView;

import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import com.tutorialsbuzz.recyclerview.R;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class TabOneFragment extends Fragment implements SearchView.OnQueryTextListener {

    private RecyclerView recyclerview;
    private List<CountryModel> mCountryModel;
    private RVAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.tab_one_fragment, container, false);

        recyclerview = (RecyclerView) view.findViewById(R.id.recyclerview);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        recyclerview.setLayoutManager(layoutManager);

        return view;
    }


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        setHasOptionsMenu(true);
        String[] locales = Locale.getISOCountries();
        mCountryModel = new ArrayList<>();

        for (String countryCode : locales) {
            Locale obj = new Locale("", countryCode);
            mCountryModel.add(new CountryModel(obj.getDisplayCountry(), obj.getISO3Country()));
        }

        adapter = new RVAdapter(mCountryModel);
        recyclerview.setAdapter(adapter);
    }


    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_main, menu);

        final MenuItem item = menu.findItem(R.id.action_search);
        final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
        searchView.setOnQueryTextListener(this);


        item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
            @Override
            public boolean onMenuItemActionCollapse(MenuItem item) {
                // Do something when collapsed
                adapter.setFilter(mCountryModel);
                return true; // Return true to collapse action view
            }

            @Override
            public boolean onMenuItemActionExpand(MenuItem item) {
                // Do something when expanded
                return true; // Return true to expand action view
            }
        });

    }

    @Override
    public boolean onQueryTextChange(String newText) {
        final List<CountryModel> filteredModelList = filter(mCountryModel, newText);
        adapter.setFilter(filteredModelList);
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        return false;
    }

    private List<CountryModel> filter(List<CountryModel> models, String query) {
        query = query.toLowerCase();

        final List<CountryModel> filteredModelList = new ArrayList<>();
        for (CountryModel model : models) {
            final String text = model.getName().toLowerCase();
            if (text.contains(query)) {
                filteredModelList.add(model);
            }
        }
        return filteredModelList;
    }
}


 

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

42 comments:

Akinyemi john said...

Good tutorials you have here am one of your fan and i really appreciate your work.
How can i add the list of country in text file/html and read it in the fragment one page. Instead of autogenerating it through ISO....
Kindly help

Saurabh Bhat said...

can you please explain how to search in normal activity without tabs

chrisaparis said...

Thank you much for the tutorial and sample source code it is much appreciated! I am attempting to implement this functionality into my application. I am wondering in this app code, where does all the data come from that is displayed in the RecyclerView?

Pawan Deshpande said...

@chrisaparis
All the data is from java Locale object "Locale.getISOCountries()" , which you can see inside onViewCreated() method

Fabio Costa said...

Thanks a lot... :)

Fabio Costa said...

Hello, can you help me please? how can I get the real position from item in the search? because it is returning 0, but without the search, it return the actual position. I need open another Activity when I click on item, but I need of the position for load the data.

// after the search i get zero position onClick method, so it load wrong data
@Override
public void onClick(View v) {
int position = getAdapterPosition();
Log.i("###--->", "position " + position);
}

sumit kumawat said...

Sir how to access all fragment content on single mainactivity
example i've 3 fragment like 3 tab(contain editext,radiobutton like resources all type)
and 1 mainactivity
i want to access data of all fragment on sigle activity

bharati ammulu said...

I have implemented RecyclerView SearchView demo which is given here. Is it possible to extend this filter to two parameters? The first parameter in my example is "product name" which results in a list of companies. My App is now ready and working on one parameter. Now I need to further filter on "City name" and display the company names for a particular city. I am thinking of placing two searchviews, one to accept productname and another searchview to accept city. Can you help me to go further from here.

Unknown said...

I have implemented RecyclerView SearchView demo which is given here. Is it possible to extend this filter to two parameters? The first parameter in my example is "product name" which results in a list of companies. My App is now ready and working on one parameter. Now I need to further filter on "City name" and display the company names for a particular city. I am thinking of placing two searchviews, one to accept productname and another searchview to accept city. Can you help me to go further from here.

Jsttel said...

Thanks a lot...

Jsttel said...

Thanks a lot. Save my time...

Unknown said...

Great tutorial! Thank you very much.

ahma said...

I try to use this way to make my application in which I have a name , phone, address and IMEI and I apply the filter on its name but does not work please explain to me where the problem can be. I apologize for the bad english

Unknown said...

I lIKE YOUR TUTORIAL

Muh Arizal Saputro said...

wow thanks

Jorge Herrera García said...

thank you very much for this tutorial! It was incredibly clear and helpful for me.

Jorge Herrera García said...

thank you very much for this tutorial. It was incredibly clear and helpful for me.

Unknown said...

superb.! thanks

Alex said...

you need to use for loop inside your onClick. compare the string from original List with filtered List. if equal, then it is the original position

iand adi said...

I have same problem, how to solve this?

Emmanuel Pacheco said...

excelente, very nice thank you... from México

Rakhi Dhavale said...

Thanks a ton, your post has taught me a lot and saved my time as a developer !

Frank said...

You need to write the setonitemclick listener on the FILTERED method TOO (specifically, on the onQueryTextChange method). Also you need to assign the model in the filteredModelList, such as:

CountryModel countrymodel = filteredModelList.get(position);

Instead of:

CountryModel countrymodel = mCountryModel.get(position);

Otherwise, you'll always get the unfiltered position or string of an item.

winfred adrah said...

hello, this is a great tutorial, but i need to add some few things and i was hoping you could help,
1) I want to add my own data to the array string instead of using the countries

2) I also want to be able to open a new activity to display the data(in your example, the countries). So anytime i click on one of the the items, it should pass the data to a new activity. Unfortunately, recyclerview does not have an OnClickActionListener.

Thank you.

Android RecyclerView With CardView - TutorialsBuzz said...

[…] RecyclerView Item Filter. […]

Android RecyclerView With Scrolling Behaviour - TutorialsBuzz said...

[…] RecyclerView Item Filter. […]

2 RecylerView with CardView OnitemClick – Android Material Design | Android Mobile said...

[…] Android Filter RecyclerView Using SearchView In ToolBar http://www.tutorialsbuzz.com/2015/11/Android-Filter-RecyclerView-Using-SearchView-In-ToolBar.html […]

Mark Warren J said...

awesome tutoria.... really easy to follow and managed to implement this feature.... was able to implement getting the real position by running a custom method to go through the original listing and the search string..

Filter RecyclerView Using SearchView In ToolBar | ASK Dev Archives said...

[…] want to create this proyect http://tutorialsbuzz.com/2015/11/android-filter-recyclerview-using-searchview-in-toolbar.html, but the problem is I can’t add my own data to the array string instead of using the […]

Desenvolvimento Em Foco said...

Congratulations, It really was AWSOME...

Monika Basarkar said...

from where you are accessing data

Mohsen Rastegari said...

hello
in the code "setOnActionExpandListener" is deprecated how i can solve this problem?

Adam Ritchie said...

Instead of "MenuItemCompat.setOnActionExpandListener", use "item.setOnActionExpandListener", or whatever the name of your MenuItem for action_search is

souhir djedidi said...

Hi Adam, it still gives me an exception java.lang.UnsupportedOperationException: This is not supported, use MenuItemCompat.setOnActionExpandListener(). please help! thanks !

Vineeth Kumar said...

how can i edit contents

shahroz said...

looking for this from many days. Working fine. Thanks dude. many Apreciation.

ankit jad said...

this is slowing down my appplication dramatically. i can only search for one item at max, when i remove the query item and start writing next, after writing 3 letters the application hangs up. whats the reason?

Irakli said...

you saved my life thank you <3

Vladimir_ said...

Where did you store your data?

Engr Denis Christopher said...

i dont see a list of countries

Hollyn Derisse said...

You are my new savior. Thanks.

Allaude Bluesky said...

can this be done in xamarin.android? what is the application used this tutorial?

Post a Comment