Android Filter RecyclerView Using SearchView In ToolBar

By | November 9, 2015

There might be requirement in your application where you want to implement the search functionality by having searchView widget inside the toolbar for filter the recyclerview . In the previous series of tutorial we have seen the basics of a recyclerView and it’s scrolling behaviour and also on recyclerView with cardview , 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 {
    compile fileTree(dir: ‘libs’, include: [‘*.jar’])
    compile ‘com.android.support:appcompat-v7:23.0.1’
    compile ‘com.android.support:design:23.0.1’
    compile ‘com.android.support:recyclerview-v7:23.0.1’
}

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="android.support.v7.widget.SearchView" 
        app:showAsAction="always|collapseActionView" />

</menu>

I 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">

    <android.support.v7.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:layout_width="match_parent" 
       android:layout_height="wrap_content" 
       android:orientation="vertical" 
       android:padding="16dp">

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

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

</LinearLayout>

 

4. Java Bean

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 android.support.v7.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;

    public RVAdapter(List<CountryModel> mCountryModel) {
        this.mCountryModel = 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 android.support.v7.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);

        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 android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.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);

        MenuItemCompat.setOnActionExpandListener(item,
                new MenuItemCompat.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;
    }
}

 

 

More Tutorial On RecyclerView

  1. RecyclerView With Scrolling Behaviour.
  2. RecyclerView Item Click Listener With Ripple Effect.
  3. RecyclerView With CardView.
  4. RecyclerView Item Filter With Animation.
  • 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

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

  • 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?

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

  • Thanks a lot… 🙂

  • 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);
    }

  • 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

  • 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.

  • 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.

  • Thanks a lot…

  • Thanks a lot. Save my time…

  • Great tutorial! Thank you very much.

  • 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

  • I lIKE YOUR TUTORIAL

  • wow thanks

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

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

  • superb.! thanks

  • 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

  • excelente, very nice thank you… from México

  • winfred adrah

    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.