One of the most requested enhancements for the Random Contact App is for a way to filter contacts
with the custom contact picker that is used in it. Since it seemed like a good reusable component, I added the module
named MultiContactPicker along with the rearchitecture of my Andorid library project lib-aeApps.
Lets see how to add a filter for a RecyclerView in an Android app.
As you should have guessed, when we apply a filter we would show a subset of the data in the list. From the RecyclerView’s point of view, the list of data that it has to show has changed - a call to notifyDataSetChanged()
is to be expected.
And where do we manage the data for a RecyclerView? The backing adapter which extends the class RecyclerView.Adapter
.
- The first step is for the adapter to implement the
android.widget.Filterable
class
class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder>
implements Filterable {
...
}
- Next, we create a copy of the source data. But use the filtered list to render the view.
private final List<ContactInfo> contactInfoList;
private List<ContactInfo> filteredContacts;
MyRecyclerViewAdapter(final List<ContactInfo> values) {
filteredContacts = new ArrayList<>(values);
contactInfoList = values;
}
- When we implementing the
Filterable
class, we need to add a method called getFilter()
. This is where the actual filtering occurs.
The performFiltering()
method is passed in a search string, and we need to filter the list data based on this. The filtering criteria
is upto your specific use case. I have added some optimizations so that our friend notifyDataSetChanged()
is only called when required. And ofcourse when the search string is empty, we copy back the original list.
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
boolean dataUpdated = false;
if (charSequence.length() == 0) {
if (filteredContacts.size() < contactInfoList.size()) {
filteredContacts = new ArrayList<>(contactInfoList);
dataUpdated = true;
}
} else {
filteredContacts = filterContactsByName(charSequence.toString().toLowerCase());
dataUpdated = true;
}
FilterResults filterResults = new FilterResults();
filterResults.values = dataUpdated;
return filterResults;
}
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
boolean dataUpdated = Boolean.parseBoolean(filterResults.values.toString());
if (dataUpdated) {
notifyDataSetChanged();
}
}
};
}
- Next step is to add a
SearchView
to the layout which includes the RecyclerView
.
<androidx.appcompat.widget.SearchView
android:id="@+id/multiContactSearchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconifiedByDefault="false"
app:queryHint="@string/str_multi_contact_search_hint" />
- And finally in the parent activity or Fragment, we respond to the QueryText of the SearchView.
Whenever there is a change in text, we simply invoke the
filter()
method on the adapter.
searchView = findViewById(R.id.multiContactSearchView);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
adapter.getFilter().filter(newText);
return false;
}
});
These are all the changes that are required. Pretty much all of the above code except the implementation for filterContactsByName()
is generic and can be re-used for any project. Please reach out to me for any doubts. See the references to the source code hosted on GitHub.
References
- Random Contact App
- lib-aeapps
- MultiContactRecyclerViewAdapter
- Sample App