Earlier I have written few articles about RecyclerView. Combining it with CardView, cloning Gmail inbox, adding Swipe to delete functionality and bunch of other.

Today, in this article we are going to learn how to add search filter functionality to RecyclerView. Adding search is very simple task, we’ll use Toolbar’s search widget to input the search query. To demonstrate I am taking an example of contacts list and search for a contact by name or phone number.

android-recyclerview-search-filter

1. RecyclerView Search Filter – getFilter()

Android provides Filterable class to filter the data by a filter (condition). Usually the getFilter() method has to be overridden in the adapter class in which the filter condition is provided to search through a list. Below is an example of getFilter() method to search a contact by name or phone number from a list of contacts.

@Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                String charString = charSequence.toString();
                if (charString.isEmpty()) {
                    contactListFiltered = contactList;
                } else {
                    List<Contact> filteredList = new ArrayList<>();
                    for (Contact row : contactList) {

                        // name match condition. this might differ depending on your requirement
                        // here we are looking for name or phone number match
                        if (row.getName().toLowerCase().contains(charString.toLowerCase()) || row.getPhone().contains(charSequence)) {
                            filteredList.add(row);
                        }
                    }

                    contactListFiltered = filteredList;
                }

                FilterResults filterResults = new FilterResults();
                filterResults.values = contactListFiltered;
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
                contactListFiltered = (ArrayList<Contact>) filterResults.values;

                // refresh the list with filtered data
                notifyDataSetChanged();
            }
        };
    }

2. Example JSON

For this example I am going to use the json from below url. This json contains list of contacts and each contact will have name, phone number and profile image.

https://api.androidhive.info/json/contacts.json

[{
		"name": "Tom Hardy",
		"image": "https://api.androidhive.info/json/images/tom_hardy.jpg",
		"phone": "(541) 754-3010"
	},
	{
		"name": "Johnny Depp",
		"image": "https://api.androidhive.info/json/images/johnny.jpg",
		"phone": "(452) 839-1210"
	}
]

3. Creating New Project

Now we’ll start with a new project in Android Studio and see how to get the desired search output.

1. Create a new project in Android Studio from File β‡’ New Project and select Basic Activity from templates.

2. Open build.gradle under app folder and add RecyclerView, Glide and Volley dependencies.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // ...

    // recycler view
    implementation 'com.android.support:recyclerview-v7:26.1.0'

    // glide image library
    implementation 'com.github.bumptech.glide:glide:4.3.1'

    // volley http library
    implementation 'com.android.volley:volley:1.0.0'
    implementation 'com.google.code.gson:gson:2.6.2'

}

3. Add the below resources to respective strings.xml, dimens.xml, colors.xml files.

<resources>
    <string name="app_name">RecyclerView Search</string>
    <string name="action_settings">Settings</string>
    <string name="toolbar_title">Contacts</string>
    <string name="action_search">Search</string>
    <string name="search_hint">Type name…</string>
</resources>
<resources>
    <dimen name="fab_margin">16dp</dimen>
    <dimen name="activity_margin">16dp</dimen>
    <dimen name="thumbnail">40dp</dimen>
    <dimen name="row_padding">10dp</dimen>
    <dimen name="contact_name">15dp</dimen>
    <dimen name="contact_number">12dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#111</color>
    <color name="colorPrimaryDark">#FFF</color>
    <color name="colorAccent">#ea3732</color>
    <color name="contact_name">#333333</color>
    <color name="contact_number">#8c8c8c</color>
</resources>

4. Download this res.zip and add the drawables to your project’s res folder. These folders contains search icon needed for the toolbar.

5. Create a class named MyApplication.java and extend the class from Application. This is a singleton class in which volley is initiated.

package info.androidhive.recyclerviewsearch;

/**
 * Created by ravi on 16/11/17.
 */

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

public class MyApplication extends Application {

    public static final String TAG = MyApplication.class
            .getSimpleName();

    private RequestQueue mRequestQueue;

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }

    public static synchronized MyApplication getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        // set the default tag if tag is empty
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

6. Open AndroidManifest.xml and add MyApplication to <application> tag. Also add the INTERNET permission as we are going to make http calls.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.recyclerviewsearch">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".MyApplication"
        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"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

7. As we need to parse the json, we need a POJO class to serialize the json. Create a class named Contact.java and add name, image and phone number.

package info.androidhive.recyclerviewsearch;

/**
 * Created by ravi on 16/11/17.
 */

public class Contact {
    String name;
    String image;
    String phone;

    public Contact() {
    }

    public String getName() {
        return name;
    }

    public String getImage() {
        return image;
    }

    public String getPhone() {
        return phone;
    }
}

8. Create a class named MyDividerItemDecoration.java. This step is completely optional but to add some margin to RecyclerView divider. This is a custom divider class to add left margin to divider line.

package info.androidhive.recyclerviewsearch;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.View;

/**
 * Created by ravi on 17/11/17.
 */

public class MyDividerItemDecoration  extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    private Drawable mDivider;
    private int mOrientation;
    private Context context;
    private int margin;

    public MyDividerItemDecoration(Context context, int orientation, int margin) {
        this.context = context;
        this.margin = margin;
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left + dpToPx(margin), top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top + dpToPx(margin), right, bottom - dpToPx(margin));
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

    private int dpToPx(int dp) {
        Resources r = context.getResources();
        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()));
    }
}

4. Writing the Adapter class with Filter

Now as the resources ready, let’s start writing the adapter class. You need to particularly focus on this class as it is main component in this article.

9. Create a layout named user_row_item.xml and add the below layout. This layout renders the single contact item in the list. This layout contains two TextViews to render name, phone number and an ImageView to display the profile image.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/selectableItemBackground"
    android:clickable="true"
    android:paddingBottom="@dimen/row_padding"
    android:paddingLeft="@dimen/activity_margin"
    android:paddingRight="@dimen/activity_margin"
    android:paddingTop="@dimen/row_padding">

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="@dimen/thumbnail"
        android:layout_height="@dimen/thumbnail"
        android:layout_centerVertical="true"
        android:layout_marginRight="@dimen/row_padding" />

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/thumbnail"
        android:fontFamily="sans-serif-medium"
        android:textColor="@color/contact_name"
        android:textSize="@dimen/contact_name" />

    <TextView
        android:id="@+id/phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/name"
        android:layout_toRightOf="@id/thumbnail"
        android:textColor="@color/contact_number"
        android:textSize="@dimen/contact_number" />

</RelativeLayout>

10. Create class named ContactsAdapter.java and implement the class from Filterable which asks you to override the getFilter() method.

> In getFilter() method, the search string is passed to performFiltering() method. The search for a contact by name or mobile number is performed using query string.

> You will have to adjust the search condition depending on your app requirement.

> ContactsAdapterListener interface provides onContactSelected() callback method whenever a contact is selected from the list.

package info.androidhive.recyclerviewsearch;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;

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

/**
 * Created by ravi on 16/11/17.
 */

public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.MyViewHolder>
        implements Filterable {
    private Context context;
    private List<Contact> contactList;
    private List<Contact> contactListFiltered;
    private ContactsAdapterListener listener;

    public class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView name, phone;
        public ImageView thumbnail;

        public MyViewHolder(View view) {
            super(view);
            name = view.findViewById(R.id.name);
            phone = view.findViewById(R.id.phone);
            thumbnail = view.findViewById(R.id.thumbnail);

            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    // send selected contact in callback
                    listener.onContactSelected(contactListFiltered.get(getAdapterPosition()));
                }
            });
        }
    }


    public ContactsAdapter(Context context, List<Contact> contactList, ContactsAdapterListener listener) {
        this.context = context;
        this.listener = listener;
        this.contactList = contactList;
        this.contactListFiltered = contactList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.user_row_item, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        final Contact contact = contactListFiltered.get(position);
        holder.name.setText(contact.getName());
        holder.phone.setText(contact.getPhone());

        Glide.with(context)
                .load(contact.getImage())
                .apply(RequestOptions.circleCropTransform())
                .into(holder.thumbnail);
    }

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

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                String charString = charSequence.toString();
                if (charString.isEmpty()) {
                    contactListFiltered = contactList;
                } else {
                    List<Contact> filteredList = new ArrayList<>();
                    for (Contact row : contactList) {

                        // name match condition. this might differ depending on your requirement
                        // here we are looking for name or phone number match
                        if (row.getName().toLowerCase().contains(charString.toLowerCase()) || row.getPhone().contains(charSequence)) {
                            filteredList.add(row);
                        }
                    }

                    contactListFiltered = filteredList;
                }

                FilterResults filterResults = new FilterResults();
                filterResults.values = contactListFiltered;
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
                contactListFiltered = (ArrayList<Contact>) filterResults.values;
                notifyDataSetChanged();
            }
        };
    }

    public interface ContactsAdapterListener {
        void onContactSelected(Contact contact);
    }
}

5. Adding Search Widget and Filtering List

Now everything is ready. All we have to do is, enable SearchView in Toolbar, render the RecyclerView by parsing the json and pass the search query to adapter.

11. Open / create menu_main.xml located under res β‡’ menus and add the SearchView widget and make it always visible.

12. Under res β‡’ xml folder, create an xml file named searchable.xml (If xml folder doesn’t exists, create a new one)

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

13. Open AndroidManifest.xml and configure the search as shown below.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.recyclerviewsearch">

    <uses-permission android:name="android.permission.INTERNET" />

    <application ...>
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">

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

14. Open the layout files of main activity activity_main.xml and content_main.xml and add RecyclerView element.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.androidhive.recyclerviewsearch.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@android:color/white"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="info.androidhive.recyclerviewsearch.MainActivity"
    tools:showIn="@layout/activity_main">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="vertical" />

</RelativeLayout>

15. Finally open the MainActivity.java and add the code as shown below.

> In fetchContacts() method a volley is request is made to fetch the json. The fetched json is searialized using Gson and all the contacts were added to a list. Calling mAdapter.notifyDataSetChanged() renders the RecyclerView.

> In onCreateOptionsMenu() the menu is inflated and SearchView is displayed.

> searchView.setOnQueryTextListener() listens to character changes while user typing in search filed. The entered query then passed to adapter class using mAdapter.getFilter().filter(query), then the RecyclerView is refreshed with filtered data.

> onContactSelected() will be called when a contact is selected from the list.

package info.androidhive.recyclerviewsearch;

import android.app.SearchManager;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import org.json.JSONArray;

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

public class MainActivity extends AppCompatActivity implements ContactsAdapter.ContactsAdapterListener {
    private static final String TAG = MainActivity.class.getSimpleName();
    private RecyclerView recyclerView;
    private List<Contact> contactList;
    private ContactsAdapter mAdapter;
    private SearchView searchView;

    // url to fetch contacts json
    private static final String URL = "https://api.androidhive.info/json/contacts.json";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // toolbar fancy stuff
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setTitle(R.string.toolbar_title);

        recyclerView = findViewById(R.id.recycler_view);
        contactList = new ArrayList<>();
        mAdapter = new ContactsAdapter(this, contactList, this);

        // white background notification bar
        whiteNotificationBar(recyclerView);

        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.addItemDecoration(new MyDividerItemDecoration(this, DividerItemDecoration.VERTICAL, 36));
        recyclerView.setAdapter(mAdapter);

        fetchContacts();
    }

    /**
     * fetches json by making http calls
     */
    private void fetchContacts() {
        JsonArrayRequest request = new JsonArrayRequest(URL,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        if (response == null) {
                            Toast.makeText(getApplicationContext(), "Couldn't fetch the contacts! Pleas try again.", Toast.LENGTH_LONG).show();
                            return;
                        }

                        List<Contact> items = new Gson().fromJson(response.toString(), new TypeToken<List<Contact>>() {
                        }.getType());

                        // adding contacts to contacts list
                        contactList.clear();
                        contactList.addAll(items);

                        // refreshing recycler view
                        mAdapter.notifyDataSetChanged();
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // error in getting json
                Log.e(TAG, "Error: " + error.getMessage());
                Toast.makeText(getApplicationContext(), "Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });

        MyApplication.getInstance().addToRequestQueue(request);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);

        // Associate searchable configuration with the SearchView
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        searchView = (SearchView) menu.findItem(R.id.action_search)
                .getActionView();
        searchView.setSearchableInfo(searchManager
                .getSearchableInfo(getComponentName()));
        searchView.setMaxWidth(Integer.MAX_VALUE);

        // listening to search query text change
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                // filter recycler view when query submitted
                mAdapter.getFilter().filter(query);
                return false;
            }

            @Override
            public boolean onQueryTextChange(String query) {
                // filter recycler view when text is changed
                mAdapter.getFilter().filter(query);
                return false;
            }
        });
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_search) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        // close search view on back button pressed
        if (!searchView.isIconified()) {
            searchView.setIconified(true);
            return;
        }
        super.onBackPressed();
    }

    private void whiteNotificationBar(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int flags = view.getSystemUiVisibility();
            flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            view.setSystemUiVisibility(flags);
            getWindow().setStatusBarColor(Color.WHITE);
        }
    }

    @Override
    public void onContactSelected(Contact contact) {
        Toast.makeText(getApplicationContext(), "Selected: " + contact.getName() + ", " + contact.getPhone(), Toast.LENGTH_LONG).show();
    }
}
android-recyclerview-search-filter

I hope this article explains very well about filtering the RecyclerView data. If you still have any queries, let’s discuss them in the comment section below.

Happy Coding πŸ˜€

Subscribe
Notify of
guest
244 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jay Patel
Jay Patel
2 years ago

today made recyclerview with filter app everything working fine but problem is occurred when i search some word and getting result correct but when on click filter result list it old position get
can u help me

Ravi Tamada
2 years ago
Reply to  Jay Patel

Do you want position of the element? I have written a callback which returns the actual object instead of position.

If you want position, you can use .indexOf() method.

Josee Naava
Josee Naava
2 years ago

exelente

Ravi Tamada
2 years ago
Reply to  Josee Naava

Thanks.

Al tegani
Al tegani
2 years ago

hi Ravi Thanks

Ravi Tamada
2 years ago
Reply to  Al tegani

you are welcome πŸ™‚

Gokul Krish
Gokul Krish
2 years ago

with rxjava it could be easy..

Ravi Tamada
2 years ago
Reply to  Gokul Krish

Giving a code example would be helpful to others.

Ramanpreet Singh
Ramanpreet Singh
2 years ago

after writing some text in search and press go button on soft keyboard give error of null itemCountSize in adapter. What to do?

Ravi Tamada
2 years ago

Can you paste the error from LogCat.

Ramanpreet Singh
Ramanpreet Singh
2 years ago
Reply to  Ravi Tamada

Thanks for replying Ravi… Your given code works perfectly fine but there is a minor issue, on pressing go, your activity just restarted and but in my application, I am getting list through intent which when on pressed go button restarts the activity and so gives null error. So I solve this problem by hiding the softkeybord on pressing go button after writing some text in searchview.

Ravi Tamada
2 years ago

Cool. Please share the code you have added.

Ravi Tamada
2 years ago

Have you tried extending your class from SugarApp instead of Application?

PauloCeami
2 years ago
Reply to  Ravi Tamada

Sugar extends Application for default

Dilshad Khan
Dilshad Khan
2 years ago

Thanks :disqus and bro i want to ask a question i’m try to do search view with fragments using viewpager (Tabs) so tell me how to do this.
comment image

Amar Ilindra
Amar Ilindra
2 years ago

I’m getting this error “java.lang.IndexOutOfBoundsException: Inconsistency detected.” when using search on large data (like 200 rows)

Ravi Tamada
2 years ago

I think some Contact name is array list is null.

Dilshad Khan
Dilshad Khan
2 years ago
Reply to  Ravi Tamada

yes u r right Ravi i solved my problem…

Bro Ravi i want to give u a task…
make an app that load large json file about 2000+ rows or more
and load it into app…

actually i searched everywhere but didn’t get any solution to fetching large json data into app….
my question is that – i want to fetch first 50 items and when user scroll down & user reach at last item of recyclerView i want to show loading spinner and than load next 50 items…
Like Google Playstore

if u do this its really v.helpfull for Android beginners like me ^_^
and one more thing really you are doing great work. when i’m facing error or want something new in app i just open open Androidhive because there is :disqus ^_^

Nur Muhammad
Nur Muhammad
2 years ago
Reply to  Dilshad Khan

Maybe you can configure your server to able getting data with pagination parameter (page 1, 2, etc…). And then in your android app you can use RecyclerView Endless Scrolling to load more data.

juna.binrusdi
juna.binrusdi
2 years ago

Hii Ravi,,,tq so much

Ravi Tamada
2 years ago
Reply to  juna.binrusdi

You are welcome πŸ™‚

Dharmendra Mishra
Dharmendra Mishra
2 years ago

Thank you so much Sir ji πŸ™‚

Ravi Tamada
2 years ago

You are welcome Mishra πŸ™‚

Parth Aggarwal
Parth Aggarwal
2 years ago

Sir Please share this whole code in Kotlin Android also.

Ravi Tamada
2 years ago
Reply to  Parth Aggarwal

You can convert the existing project to Kotlin in Android Studio.
https://developer.android.com/kotlin/get-started.html#convert-to-kotlin-code

Atharva Unde
Atharva Unde
2 years ago

Can u make a detailed tutorial on google transport tracking system ?

Email Subjekt
Email Subjekt
2 years ago

Hi Ravi, thank you for the code! Is there a way to use this in a fragment?

Roeun Chhengheng
Roeun Chhengheng
2 years ago

Thank you for this code! If I want to use json for offline, what can i do?

ahmed azeema
ahmed azeema
2 years ago

thank u Ravi but how can i make detail activity onclick ?

Juliet Wanjohi
Juliet Wanjohi
2 years ago

:disqus Hi, I have been able to make the search view work on my paginated recyclerview. However, the user is only able to get a search result after loading all the pages in the recyclerview. How do I make a search result return even if only one page has been loaded?

Ravi Tamada
2 years ago
Reply to  Juliet Wanjohi

Then the search has to be done on the server instead of local. You can send the search query to sever and perform search there, return the results.

Jerry Yu
Jerry Yu
2 years ago

For whatever reason, we have to call searchView.setIconified(true) twice to iconify the searchView. This information could be useful for other readers.

Ravi Tamada
2 years ago
Reply to  Jerry Yu

Thank you Jerry.

nikul
nikul
2 years ago

this.contactList = contactList;
this.contactListFiltered = contactList;
both refer to the same memory location then why need extra variable

Sharath
Sharath
2 years ago

i am getting null pointer exception????

Ravi Tamada
2 years ago

Is the device has Internet connection?

Sharath
Sharath
2 years ago
Reply to  Ravi Tamada

yes

Ravi Tamada
2 years ago
Reply to  Sharath

See if the json is fetched correctly from server. Print some Logs and check.

le anhvu
le anhvu
2 years ago

thank you so much

Ravi Tamada
2 years ago
Reply to  le anhvu

you are welcome πŸ™‚

Programmer Java
Programmer Java
2 years ago

thank you so much..
I can Use this code with php and database?

Ravi Tamada
2 years ago

Yes, you need to build Rest api for search endpoint.

Aviv Zikel
Aviv Zikel
2 years ago

I have node.js server, is it possible to connect it to the search option? I mean that the results of the searching(in the search view) will be shown from the db which connected to the node.js server

Aditi Gupta
Aditi Gupta
2 years ago

Hi Ravi….this is the great article.
My question is I am trying to integrate alphabet side index with this article to make it like contact list in our phone,but I am unable to do it.Can you please help?

Ravi Tamada
2 years ago
Reply to  Aditi Gupta
Aditi Gupta
Aditi Gupta
2 years ago
Reply to  Ravi Tamada

Yes…I am trying to do like this only,but unable to do it.

Ravi Tamada
2 years ago
Reply to  Aditi Gupta

Have you tried using those libraries?

Aditi Gupta
Aditi Gupta
2 years ago
Reply to  Ravi Tamada

Yes I have tried, but its not working

Ravi Tamada
2 years ago
Reply to  Aditi Gupta

They should work. You probably needs more time and practice to make them work.

Geneva Daugdaug
Geneva Daugdaug
2 years ago

Attempt to invoke interface method ‘int java.util.List.size()’ on a null object reference.

I have this error.

Ravi Tamada
2 years ago

What is the code at error line. You can find the file name along with file name in LogCat.

Kevin Moses Kenap
Kevin Moses Kenap
2 years ago

Hello bro, i wanna ask you, if the data in this tutorial retrieve from firebase database can you teach me how?

Jayaprakash
Jayaprakash
2 years ago

Hi bro, This code is working like charm but the issue was if I access from the local database its showing the result if I do with the API response its not at all filtering

Ravi Tamada
2 years ago
Reply to  Jayaprakash

Wherever the data is coming from, it will be in ArrayList only. So it should work. Is API response is fetch each time something is typed in search?

Jojo
Jojo
2 years ago

Thank you

Ravi Tamada
2 years ago
Reply to  Jojo

You are welcome:)

Ashis Ranjan Dey
Ashis Ranjan Dey
2 years ago

does this search work if I search with lastname or middlename?

Ravi Tamada
2 years ago

That depends on the logic you are writing in adapter class. Check getFilter() method in adapter class.

Daily Wiki
Daily Wiki
2 years ago

Want to load Webview on Click, could someone please help with that I m new to android studio

Karthikesavan
Karthikesavan
2 years ago

For me the code works till, when i type the text in search box, i can able to toast the letter on each change, but in your code you have used gson, but in mine its StringRequest, so im stuck in this point, so how to go further? any suggestions?comment image

Ravi Tamada
2 years ago
Reply to  Karthikesavan

Gson part is completely a different topic in the article. It’s the way you parse the JSON, nothing do with Search. What is not working in your code?

Karthikesavan
Karthikesavan
2 years ago
Reply to  Ravi Tamada

As i can see, in your code, u are clearing the list and again adding the list, comment image so I’m wondering how to do this in my code !!, So I guess without adding this nothing is happening, no filter is happening..

Ravi Tamada
2 years ago
Reply to  Karthikesavan

Post your code on gist. I can check.

Alaa_Alshorbagy
Alaa_Alshorbagy
2 years ago

Hi Ravi Tamada
i have an exception happened in performFiltering() function
“Java.util.ConcurrentModificationException”
and this lead me to this line of code in ContactsAdapter class and
my code match your code and after search found it’s prefer to use Iterator when add or remove in arrayList
and change this segment of code to using iterator and the same exception happen
please any help
thanks for you time.

exception happen here —>>> for (Contact row : contactList) {

// name match condition. this might differ depending on your requirement
// here we are looking for name or phone number match
if (row.getName().toLowerCase().contains(charString.toLowerCase()) || row.getPhone().contains(charSequence)) {
filteredList.add(row);
}
}

dwi
dwi
2 years ago

i have error like this to

Ravi Tamada
2 years ago
Reply to  dwi

Post your complete error report along with Adapter class code.

Alaa_Alshorbagy
Alaa_Alshorbagy
2 years ago

Hi Ravi Tamada,
as per your code when i press on submit search view display filtered names only ok?
but when you press on submit search view it display all contacts
or how to do this ?

Ravi Tamada
2 years ago

Any screenshot?

dwi
dwi
2 years ago

thanks for your tutorial, but i have error on Filter Results
for(Contact row : contactList)
the error is row must be Object type.
what sould i do?

Ravi Tamada
2 years ago

Hi Amrit

These are static JSON files created for article demonstrations. If you want to build the JSON from a database, read the below article.
https://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-12-2/

Josee Naava
Josee Naava
2 years ago

How do you add empty state when there are no results?

Ravi Tamada
2 years ago
Reply to  Josee Naava

Easier way to place Empty state view on top of RecyclerView and show it when the result set size is 0. The other way is, having empty state as a row in RecyclerView itself and show it when there is no data. But this approach a bit difficult and it needs lot of changes in Adapter class.

LakshmiRao Burugula
LakshmiRao Burugula
2 years ago

Hello Ravi, thank you for this amazing post. I want to search support for Arabic RTL. In the Manifest you did mention “android:supportsRtl=”true”” but search view looks the same as English on Arabic language. I have included the screenshot of search in Arabic, search icon and the cursor should start from the right when we start to type. Could you please guide on how to get that working please

Ravi Tamada
2 years ago

Hi

Where is the screenshot?

LakshmiRao Burugula
LakshmiRao Burugula
2 years ago
Reply to  Ravi Tamada

comment image

LakshmiRao Burugula
LakshmiRao Burugula
2 years ago
Reply to  Ravi Tamada

Hi Ravi, I will try and and will update you. thank you for sharing that link.

Ravi Tamada
2 years ago

Okay.

Rizky Nofriwandi
Rizky Nofriwandi
2 years ago

Hai Ravi,
Thank u for you amazing Post
i follow your tutorial but when i’m trying search it give me the correct result but when i delete the Text on SearchView recyclerview not back to view All the Data..

Can u tell me where’s my mistake?

comment image comment image comment image

AN
AN
2 years ago

Hi, I tried your code, it always displays only the first item in the recycler view, irrespective of what I search for. I can’t figure out where I might’ve gone wrong?

Ravi Tamada
2 years ago
Reply to  AN

Check the adapter class. You might have used wrong array list while searching.

Ebenezer Agyemang Sakyi
Ebenezer Agyemang Sakyi
2 years ago
Reply to  AN

Where you able to solve it? Facing the same issue

Hardik sharma
Hardik sharma
2 years ago

Hey Ravi how to use the back arrow button in this search view.

redlynxks
redlynxks
2 years ago

Can I ask why I’m getting java.lang.NullPointerException: Attempt to invoke interface method ‘int java.util.List.size()’ on a null object reference?

I’m using your filtercode but I’m having this error I don’t know why?
comment image
comment image
comment image
comment image

Error
comment image

Anele
Anele
2 years ago

I want to load a CallIntent and SmsIntent when you have selected a contact on this line of code on the Mainactivity.java

@Override
public void onContactSelected(Contact contact) {
Toast.makeText(getApplicationContext(), “Selected: ” + contact.getName() + “, ” + contact.getPhone(), Toast.LENGTH_LONG).show();
}

Anele
Anele
2 years ago
Reply to  Ravi Tamada

Yes i tried to add the CallIntent but its not able to make a phone on the selected number

Ravi Tamada
2 years ago
Reply to  Anele

Is there any error? Providing some debugging info will help.

Duhamel
Duhamel
2 years ago

Thanks to you, I finally done it after wasting a whole weekend on the official doc.
Thank you

Ravi Tamada
2 years ago
Reply to  Duhamel

Great πŸ˜›

OmkarK
OmkarK
2 years ago

You with your every Tutorial Put a Smile on my Face πŸ˜€ …! Thank you for sharing such valuable Knowledge.. πŸ™‚

Ravi Tamada
2 years ago
Reply to  OmkarK

I am glad the blog is helping you πŸ™‚

All the best!

Ebenezer Agyemang Sakyi
Ebenezer Agyemang Sakyi
2 years ago

Hi, I tried your code, it always displays only the first item in the recycler view, irrespective of what I search for. I can’t figure out where I might’ve gone wrong?

Ravi Tamada
2 years ago

Verify your getfilter method in adapter. You might be accessing wrong array list.

Joseph Joey
Joseph Joey
2 years ago
Reply to  Ravi Tamada

I am getting the same problem in both response list and search result even after replacing my adapter code with yours

Chris
Chris
2 years ago

great tutorial, thanks. Very helpful – a few little tweaks I had to make but got it working. Would be useful to add above I think that you could also create the json to be filtered from a php file – which creates the JSON dynamically. As far as I know this is the only very up-to-date tutorial for what it sets out to acheive – I mean it uses Volley and recyclerView. All others I saw – use asynctask/ httpConnection/listview etc…

Ravi Tamada
2 years ago
Reply to  Chris

Thanks Chris.

I have written same using RxJava with server search integration as you requested. Please check the article here
https://www.androidhive.info/RxJava/android-rxjava-instant-search-local-remote-databases/

Demo: https://www.youtube.com/watch?v=LhwfyBE5pqk

Mohammad Ali Kadiwala
Mohammad Ali Kadiwala
2 years ago

i have no any JSON data so can i use this for a any one like i have a static data so ?

Joseph Joey
Joseph Joey
2 years ago

Hi Ravi, I am getting only one item in both the response list in activity and search result list even after replacing my adapter code with yours. What went wrong?

richard
richard
2 years ago

Nice tutorial worked for me

Ravi Tamada
2 years ago
Reply to  richard

Thanks

244
0
Would love your thoughts, please comment.x
()
x