Complete Guide to Implementing Real-time RecyclerView Filtering with SearchView

Nov 22, 2025 · Programming · 9 views · 7.8

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.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.