Keywords: Android | RecyclerView | SearchView | Data Filtering | SortedList
Abstract: This comprehensive article details how to implement real-time data filtering in RecyclerView using SearchView in Android applications. Covering everything from basic SearchView configuration to optimized RecyclerView.Adapter implementation, it explores efficient data management with SortedList, proper usage of Filterable interface, and complete solutions for responsive search functionality. The article compares traditional filtering approaches with modern SortedList-based methods to demonstrate how to build fast, user-friendly search experiences.
SearchView Configuration and Integration
To implement search functionality in Android applications, first configure the SearchView component in the menu resource file. Create a menu file named menu_main.xml with the following content:
<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:title="@string/action_search"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always"/>
</menu>
In the Activity, override the onCreateOptionsMenu method to initialize SearchView and set up query text listeners:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
final MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setOnQueryTextListener(this);
return true;
}
@Override
public boolean onQueryTextChange(String query) {
// Implement filtering logic
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
Data Model Design
To support efficient searching and sorting, data models need to implement appropriate comparison and equality checking methods. Here's a basic model class implementation:
public class ExampleModel {
private final long mId;
private final String mText;
public ExampleModel(long id, String text) {
mId = id;
mText = text;
}
public long getId() { return mId; }
public String getText() { return mText; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExampleModel model = (ExampleModel) o;
if (mId != model.mId) return false;
return mText != null ? mText.equals(model.mText) : model.mText == null;
}
@Override
public int hashCode() {
int result = (int) (mId ^ (mId >>> 32));
result = 31 * result + (mText != null ? mText.hashCode() : 0);
return result;
}
}
SortedList-based Adapter Implementation
SortedList is a powerful tool in the RecyclerView library that efficiently manages data changes and automatically notifies the Adapter of updates. Here's the complete Adapter implementation:
public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {
private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class,
new SortedList.Callback<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
});
private final LayoutInflater mInflater;
private final Comparator<ExampleModel> mComparator;
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
mInflater = LayoutInflater.from(context);
mComparator = comparator;
}
@Override
public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
public void onBindViewHolder(ExampleViewHolder holder, int position) {
final ExampleModel model = mSortedList.get(position);
holder.bind(model);
}
public void add(ExampleModel model) {
mSortedList.add(model);
}
public void remove(ExampleModel model) {
mSortedList.remove(model);
}
public void add(List<ExampleModel> models) {
mSortedList.addAll(models);
}
public void remove(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (ExampleModel model : models) {
mSortedList.remove(model);
}
mSortedList.endBatchedUpdates();
}
public void replaceAll(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (int i = mSortedList.size() - 1; i >= 0; i--) {
final ExampleModel model = mSortedList.get(i);
if (!models.contains(model)) {
mSortedList.remove(model);
}
}
mSortedList.addAll(models);
mSortedList.endBatchedUpdates();
}
@Override
public int getItemCount() {
return mSortedList.size();
}
}
ViewHolder Implementation
The ViewHolder is responsible for binding data to views. Here's a ViewHolder implementation using data binding:
public class ExampleViewHolder extends RecyclerView.ViewHolder {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
public void bind(ExampleModel item) {
mBinding.setModel(item);
}
}
Filtering Logic Implementation
The core search filtering logic is implemented through the following method, supporting case-insensitive text matching:
private static List<ExampleModel> filter(List<ExampleModel> models, String query) {
final String lowerCaseQuery = query.toLowerCase();
final List<ExampleModel> filteredModelList = new ArrayList<>();
for (ExampleModel model : models) {
final String text = model.getText().toLowerCase();
if (text.contains(lowerCaseQuery)) {
filteredModelList.add(model);
}
}
return filteredModelList;
}
Performance Optimization and Best Practices
When implementing search functionality, pay attention to the following key performance optimization points:
Use beginBatchedUpdates() and endBatchedUpdates() to batch process multiple data changes, which can significantly improve performance and reduce unnecessary UI refreshes. When filtering large datasets, ensure complex matching logic is executed outside the main thread to avoid blocking the UI thread.
For large datasets, consider implementing more efficient search algorithms such as using Trie trees or pre-built indexes. Also, use scrollToPosition(0) appropriately to ensure users can see all relevant results during searching.
Traditional Filtering Method Comparison
In addition to the modern SortedList-based approach, traditional filtering implementations look like this:
public void filter(String text) {
items.clear();
if (text.isEmpty()) {
items.addAll(itemsCopy);
} else {
text = text.toLowerCase();
for (PhoneBookItem item : itemsCopy) {
if (item.name.toLowerCase().contains(text) ||
item.phone.toLowerCase().contains(text)) {
items.add(item);
}
}
}
notifyDataSetChanged();
}
While this approach is straightforward, it lacks the automatic sorting and efficient change notification mechanisms provided by SortedList, resulting in poorer performance when handling dynamic data.
Practical Application Scenarios
This search filtering pattern is widely used in various Android application scenarios, including contact lists, product catalogs, message histories, and more. With proper implementation, it can provide smooth user experiences supporting real-time search result display and dynamic data updates.