Search Filter In Recyclerview Android [Step by Step]

SearchView which can be used to filter the data displayed in the RecyclerView. While use Toolbar’s search widget to input the search query in android.

SearchView is a widget that provides a user interface for the user to enter a search query and submit a request to a search provider. Shows a list of query suggestions or results, if available, and allows the user to pick a suggestion or result to launch into.

In this tutorial, we are using recyclerview and cardview to list all the items. But I am not going to explaining about that here. Already, I have create post on that. If needed plese check it below links.

This is the example demo for search filter in recyclerview. below we will see how to implement the same.

Lets get started.

1. Adding Dependencies

As mentioned above, we are using recyclerview and cardview to hold the items. Also, we are using retrofit to fetch data from the URL. I have explained about retrofit in different post.

Retrofit android example kotlin[step by step]

Cardview Android Example [beginners]

dependencies {
    ...
    implementation 'com.android.support:design:26.1.0'
    implementation 'com.android.support:cardview-v7:26.1.0'
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'com.github.bumptech.glide:glide:4.3.1'
}

I am using Glide library to load the images from URL. 

Glide Library - Image loading library for Android

Also, Open AndroidManifest.xml, add INTERNET permission as we are going to make HTTP calls.

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

2. Create Search Filter UI

To setup searchView in toolbar, we need to add searchview as a menu items.

So, we need to create a menu with searchview.

1. Create New Menu Resource File.

goto File > New > Android Resource File.

2. Add the Searchview as a menu Item to the menu file.

<menu 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"
    tools:context=".ui.MainActivity">
    <item
        android:id="@+id/action_search"
        android:icon="@drawable/ic_launcher_background"
        android:orderInCategory="100"
        android:title="@string/action_search"
        app:showAsAction="always"
        app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

In the menu item, we need to set app:actionViewClass as a android.support.v7.widget.SearchView .

When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it needs to be set to iconified by default using setIconifiedByDefault(true). This is the default, so nothing needs to be done.

If you want the search field to always be visible, then call setIconifiedByDefault(false).

3. Inflate Searchview menu to the MainActivity. So that, we can see the searchview in the toolbar. 

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

        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);
        
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_search) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

3. Setup Recyclerview with Data From API

I am using retrofit to fetch data from API. I have explained about retrofit in my another post.

So, directly i am moving to coding part.

First, Create retrofit API client.

ApiClient.java

public class ApiClient {
    public static String BASE_URL ="http://velmm.com/apis/";
    private static Retrofit retrofit;
    public static Retrofit getClient(){
        if(retrofit == null){
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

Next, Create Model class to hold the data for the recyclerview.

Movie.java

public class Movie {
    @SerializedName("title")
    private String title;

    @SerializedName("image")
    private String imageUrl;

    public Movie(String title, String imageUrl) {
        this.title = title;
        this.imageUrl = imageUrl;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }
}

create the interface for the API call.

ApiInterface.java

public interface ApiInterface {
    @GET("volley_array.json")
    Call<List<Movie>> getMovies();
}

4. Adding Search Filter to Recyclerview

Android provides Filterable class to filter the data based on the condition.

Filterable classes are usually Adapter implementations.  

getFilter () - Returns a filter that can be used to constrain data with a filtering pattern.

So, let implement Filterable in our recyclerview adapter and filter the  data as per the condition we have. In our case, condition going to be the text we are entering onto the searchview.

public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MyViewHolder> implements Filterable {

    private List<Movie> movieList;
    private List<Movie> movieListFiltered;
    private Context context;

    public void setMovieList(Context context,final List<Movie> movieList){
        this.context = context;
        if(this.movieList == null){
            this.movieList = movieList;
            this.movieListFiltered = movieList;
            notifyItemChanged(0, movieListFiltered.size());
        } else {
            final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                @Override
                public int getOldListSize() {
                    return MovieAdapter.this.movieList.size();
                }

                @Override
                public int getNewListSize() {
                    return movieList.size();
                }

                @Override
                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                    return MovieAdapter.this.movieList.get(oldItemPosition).getTitle() == movieList.get(newItemPosition).getTitle();
                }

                @Override
                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {

                    Movie newMovie = MovieAdapter.this.movieList.get(oldItemPosition);

                    Movie oldMovie = movieList.get(newItemPosition);

                    return newMovie.getTitle() == oldMovie.getTitle() ;
                }
            });
            this.movieList = movieList;
            this.movieListFiltered = movieList;
            result.dispatchUpdatesTo(this);
        }
    }

    @Override
    public MovieAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.movielist_adapter,parent,false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(MovieAdapter.MyViewHolder holder, int position) {
        holder.title.setText(movieListFiltered.get(position).getTitle());
        Glide.with(context).load(movieList.get(position).getImageUrl()).apply(RequestOptions.centerCropTransform()).into(holder.image);
    }

    @Override
    public int getItemCount() {

        if(movieList != null){
            return movieListFiltered.size();
        } else {
            return 0;
        }
    }

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                String charString = charSequence.toString();
                if (charString.isEmpty()) {
                    movieListFiltered = movieList;
                } else {
                    List<Movie> filteredList = new ArrayList<>();
                    for (Movie movie : movieList) {
               if (movie.getTitle().toLowerCase().contains(charString.toLowerCase())) {
                            filteredList.add(movie);
                        }
                    }
                    movieListFiltered = filteredList;
                }

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

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

                notifyDataSetChanged();
            }
        };
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {

        TextView title;
        ImageView image;

        public MyViewHolder(View view) {
            super(view);
            title = (TextView) view.findViewById(R.id.title);
            image = (ImageView)view.findViewById(R.id.image);
        }
    }
}

Now, our recyclerview adapter is ready. lets put the data into the recyclerview.

recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        movieAdapter = new MovieAdapter();
        recyclerView.setAdapter(movieAdapter);

        movieList = new ArrayList<>();
        ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
        Call<List<Movie>> call = apiService.getMovies();

        call.enqueue(new Callback<List<Movie>>() {
            @Override
            public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
                movieList = response.body();
                Log.d("TAG","Response = "+movieList);
                movieAdapter.setMovieList(getApplicationContext(),movieList);
            }

            @Override
            public void onFailure(Call<List<Movie>> call, Throwable t) {
                Log.d("TAG","Response = "+t.toString());
            }
        });

Finally, we need to filter the recyclerview items based on the search filter.

lets add the listener for the searchview setOnQueryTextListener.

Once, we get the input data we need to filter the recyclerview item by adding getFilter().filter(query) in your adapter.

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

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

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                movieAdapter.getFilter().filter(query);
                return false;
            }

            @Override
            public boolean onQueryTextChange(String query) {
                movieAdapter.getFilter().filter(query);
                return false;
            }
        });
        return true;
    }

 

 

That's it. we have implemented the search filter in recyclerview.

  Share with

Conclusion

Thanks for reading. 

Please try this example and let me know your feedback in comments.

Please share if you like it.

Leave a Reply

Your email address will not be published. Required fields are marked *