RecyclerView with SearchView in Android: Full Working Guide
Search is one of the most common features in any app with a list. Type a few letters, and the list filters down to what matters. In this guide, I will show you how to build RecyclerView with SearchView in Android — a custom adapter that implements Filterable, a SearchView that triggers the filter, and full working Java code you can drop into any project.
What is RecyclerView with SearchView?
SearchView is a widget that shows a text field with a search icon. As the user types, it fires a callback with the current text.
RecyclerView is the modern list widget that shows a scrolling list of items using a RecyclerView.Adapter.
When you combine them, the SearchView filters the list in real time. The flow is:
- The user types into the SearchView
- SearchView calls
onQueryTextChange - The adapter's
getFilter().filter(newText)runs performFilteringbuilds a filtered list on a background threadpublishResultsupdates the adapter and callsnotifyDataSetChanged()- RecyclerView redraws with the filtered items
Each part has one job. SearchView captures input. The filter does the work. The adapter shows the result.
What the output looks like
Here is what you get at the end:
- A SearchView at the top with the hint "Search Here"
- Below it, a list of student cards showing roll, name, marks, and email
- Typing in the SearchView filters the list by student name
- Clearing the search restores the full list
Output screen 1 — the full student list
Output screen 2 — the list filtered by name
The data is hardcoded in MainActivity for simplicity. In a real app you would fetch it from a database or API, but the SearchView and Filterable adapter would work the same way.
Add dependencies in Gradle
Open your app-level build.gradle file and add these lines inside the dependencies block:
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
What each one does:
- material — provides Material components and the CardView used in the item layout
- recyclerview — provides the RecyclerView widget and its adapter base classes
If your project uses AndroidX (which it should, for any modern app), these are the correct dependency names. If you are still on the old Support Library, use com.android.support:recyclerview-v7:28.0.0 instead — but upgrading to AndroidX is strongly recommended.
AndroidManifest.xml setup
The manifest needs the application entry and the MainActivity declared as the launcher.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld">
<application
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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
No special permissions needed — this app does not use the network or storage.
Student.java — the data model
The Student class holds four fields: name, marks, roll, and email. It has two constructors — one with all fields and one empty — plus getters and setters.
package com.example.helloworld;
public class Student {
private String name;
private float marks;
private int roll;
private String email;
public Student(String name, float marks, int roll, String email) {
this.name = name;
this.marks = marks;
this.roll = roll;
this.email = email;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMarks() {
return marks;
}
public void setMarks(float marks) {
this.marks = marks;
}
public int getRoll() {
return roll;
}
public void setRoll(int roll) {
this.roll = roll;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Keeping the data holder simple like this is a good practice. The Student class does not know about RecyclerView or SearchView. It just carries data.
MainActivity.java — wiring the SearchView
MainActivity sets up the SearchView, the RecyclerView, builds dummy data, and wires the SearchView's query listener to the adapter's filter.
package com.example.helloworld;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.SearchView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private SearchView searchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
setDummyData();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
final CustomStudentAdapter customStudentAdapter = new CustomStudentAdapter(studentList);
recyclerView.setAdapter(customStudentAdapter);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
customStudentAdapter.getFilter().filter(newText);
return false;
}
});
}
private void initViews() {
searchView = findViewById(R.id.searchitem);
recyclerView = findViewById(R.id.myrec);
}
private List<Student> studentList = new ArrayList<>();
private void setDummyData() {
Student s1 = new Student("Arpit", 67.5f, 1, "arpit@gmail.com");
Student s2 = new Student("Vijay", 57.5f, 2, "vijay@gmail.com");
Student s3 = new Student("Sonu", 76.5f, 3, "sonu@gmail.com");
Student s4 = new Student("Rahul", 59.5f, 4, "rahul@gmail.com");
Student s5 = new Student("Deep", 66.5f, 5, "deep@gmail.com");
Student s6 = new Student("Suresh", 77.5f, 6, "suresh@gmail.com");
Student s7 = new Student("Ashok", 49.5f, 7, "ashok@gmail.com");
Student s8 = new Student("Seema", 88.5f, 8, "seema@gmail.com");
Student s9 = new Student("Anamika", 55.5f, 9, "anamika@gmail.com");
Student s10 = new Student("Chandan", 69.5f, 10, "chandan@gmail.com");
Student s11 = new Student("Syed", 70.5f, 11, "syed@gmail.com");
Student s12 = new Student("Salim", 71.5f, 12, "salim@gmail.com");
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);
studentList.add(s4);
studentList.add(s5);
studentList.add(s6);
studentList.add(s7);
studentList.add(s8);
studentList.add(s9);
studentList.add(s10);
studentList.add(s11);
studentList.add(s12);
}
}
Let's walk through it:
onCreate
Calls initViews() to find the SearchView and RecyclerView. Builds the dummy list. Sets a LinearLayoutManager on the RecyclerView. Creates the adapter and attaches it. Then wires the SearchView listener.
onQueryTextChange
This is the important method. Every time the text changes, it calls customStudentAdapter.getFilter().filter(newText). That triggers the filter chain in the adapter.
onQueryTextSubmit
We return false and do nothing. We do not need to handle the "submit" event because onQueryTextChange already filters as the user types.
Note: the original code used SearchView on an AppCompatActivity, which works, but modern Android provides a SearchView in the Material library that looks better. You can use either.
CustomStudentAdapter.java — the Filterable adapter
The adapter is the heart of the search. It implements Filterable, which means it provides a Filter object that does the actual filtering.
package com.example.helloworld;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class CustomStudentAdapter extends RecyclerView.Adapter<CustomStudentAdapter.MyView>
implements Filterable {
private List<Student> studentList;
private List<Student> orignalstudentList;
public CustomStudentAdapter(List<Student> studentList) {
this.studentList = studentList;
this.orignalstudentList = studentList;
}
@NonNull
@Override
public MyView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new MyView(view);
}
@Override
public void onBindViewHolder(@NonNull MyView holder, int position) {
Student student = orignalstudentList.get(position);
holder.troll.setText(String.valueOf(student.getRoll()));
holder.tname.setText(student.getName());
holder.tmarks.setText(String.valueOf(student.getMarks()));
holder.temail.setText(student.getEmail());
}
@Override
public int getItemCount() {
return orignalstudentList.size();
}
@Override
public Filter getFilter() {
return myFilter;
}
private Filter myFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint == null || constraint.length() == 0) {
orignalstudentList = studentList;
} else {
List<Student> filteredList = new ArrayList<>();
String filterPattern = constraint.toString().toLowerCase().trim();
for (Student student : studentList) {
if (student.getName().toLowerCase().startsWith(filterPattern)) {
filteredList.add(student);
}
}
orignalstudentList = filteredList;
}
FilterResults results = new FilterResults();
results.values = orignalstudentList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
orignalstudentList = (List) results.values;
notifyDataSetChanged();
}
};
class MyView extends RecyclerView.ViewHolder {
private TextView troll, tname, tmarks, temail;
public MyView(@NonNull View itemView) {
super(itemView);
troll = itemView.findViewById(R.id.roll);
tname = itemView.findViewById(R.id.name);
tmarks = itemView.findViewById(R.id.marks);
temail = itemView.findViewById(R.id.email);
}
}
}
Let's break down the key parts:
The two lists
studentList holds the original, complete list. It never changes. orignalstudentList holds the currently visible list — it starts as the same reference, but gets replaced by filtered results. (Yes, the original code has a typo: "orignal" instead of "original". I kept it to match the original tutorial, but you should spell it correctly in your own code.)
onBindViewHolder
Reads from orignalstudentList, not studentList. This is crucial — if it read from the original list, filtering would appear to have no effect.
performFiltering
Runs on a background thread. If the search text is empty, it restores the full list. Otherwise it loops through the original list and adds students whose name starts with the search text. Case-insensitive thanks to toLowerCase() on both sides.
publishResults
Runs on the main thread. Replaces the currently visible list with the filtered one and calls notifyDataSetChanged() so the RecyclerView redraws.
Layouts: activity_main.xml, item.xml, edit_back.xml
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<SearchView
android:id="@+id/searchitem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
android:background="@drawable/edit_back"
android:iconifiedByDefault="false"
android:queryHint="Search Here" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/myrec"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:layout_marginLeft="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Note: the original layout uses android:layout_alignParentTop, which is a RelativeLayout attribute — it does not work inside a LinearLayout. It is harmless (ignored), but you can safely remove it. I have left it here to stay close to the original.
item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
app:cardMaxElevation="10dp"
app:cardUseCompatPadding="true">
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*">
<TableRow>
<TextView
android:text="Roll : "
android:textStyle="bold" />
<TextView android:id="@+id/roll" />
</TableRow>
<TableRow>
<TextView
android:text="Name : "
android:textStyle="bold" />
<TextView android:id="@+id/name" />
</TableRow>
<TableRow>
<TextView
android:text="Marks : "
android:textStyle="bold" />
<TextView android:id="@+id/marks" />
</TableRow>
<TableRow>
<TextView
android:text="Email : "
android:textStyle="bold" />
<TextView android:id="@+id/email" />
</TableRow>
</TableLayout>
</androidx.cardview.widget.CardView>
edit_back.xml
A simple shape drawable that gives the SearchView a thin black border.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="@color/black" />
</shape>
Colors, strings, and styles
res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<color name="darkyellow">#D19F09</color>
<color name="white">#fff</color>
<color name="black">#000</color>
</resources>
res/values/strings.xml
<resources>
<string name="app_name">RecyclerView SearchView</string>
</resources>
res/values/styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
Standard Material theme setup. If you want the newer Material look, switch to Theme.MaterialComponents.Light.DarkActionBar.
Common questions
What is RecyclerView with SearchView in Android?
RecyclerView with SearchView is a UI pattern where a SearchView at the top filters the list shown by a RecyclerView as the user types. The adapter implements Filterable to perform the filtering.
What is Filterable in Android?
Filterable is an interface with a getFilter() method. Any adapter that implements Filterable can be filtered by a SearchView. The filter runs in the background and updates the list when done.
How does performFiltering work?
performFiltering runs on a background thread. It reads the search text, loops through the original list, and builds a filtered list. It returns a FilterResults object containing the filtered data.
How does publishResults work?
publishResults runs on the main thread. It takes the FilterResults from performFiltering, updates the adapter's list, and calls notifyDataSetChanged() so the RecyclerView redraws.
Why do I need to keep the original list?
You need the original list so you can restore all items when the search text is cleared. Always keep both a studentList (original) and an orignalstudentList (currently shown) reference.
Which dependency is required for CardView and RecyclerView?
You need the Material Components library and the RecyclerView library. Add com.google.android.material:material:1.1.0 and androidx.recyclerview:recyclerview:1.1.0 to your app-level build.gradle.
Where to go next
You now have a complete, working RecyclerView with SearchView in Android. You know the Gradle dependencies, the layout setup, the Student model, the MainActivity wiring, and the Filterable adapter logic.
If you remember only one thing, remember this: The adapter implements Filterable. SearchView calls getFilter().filter(text) on every keystroke. performFiltering runs in the background, publishResults updates the list on the main thread, and notifyDataSetChanged redraws the RecyclerView.
The real learning happens when you run this in Android Studio. Type in the SearchView. Watch the list filter down. Clear the search and see it restore. Then change the filter logic — try contains instead of startsWith, or filter by email instead of name. That is how the pattern becomes second nature.
If this helped, bookmark it. Next, try combining this with a Retrofit API call — fetch the list from the server and then filter it locally with the same Filterable pattern.
No comments:
Post a Comment