I am trying to implement the SearchView
from the support library. I want the user to be to use the SearchView
to filter a List
of movies in a RecyclerView
.
I have followed a few tutorials so far and I have added the SearchView
to the ActionBar
, but I am not really sure where to go from here. I have seen a few examples but none of them show results as you start typing.
This is my MainActivity
:
public class MainActivity extends ActionBarActivity {
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
RecyclerView.Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new CardAdapter() {
@Override
public Filter getFilter() {
return null;
}
};
mRecyclerView.setAdapter(mAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
And this is my Adapter
:
public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {
List<Movie> mItems;
public CardAdapter() {
super();
mItems = new ArrayList<Movie>();
Movie movie = new Movie();
movie.setName("Spiderman");
movie.setRating("92");
mItems.add(movie);
movie = new Movie();
movie.setName("Doom 3");
movie.setRating("91");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers");
movie.setRating("88");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 2");
movie.setRating("87");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 3");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Noah");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 2");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 3");
movie.setRating("86");
mItems.add(movie);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Movie movie = mItems.get(i);
viewHolder.tvMovie.setText(movie.getName());
viewHolder.tvMovieRating.setText(movie.getRating());
}
@Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView tvMovie;
public TextView tvMovieRating;
public ViewHolder(View itemView) {
super(itemView);
tvMovie = (TextView)itemView.findViewById(R.id.movieName);
tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
}
}
}
I have solved the same problem using the link with some modifications in it. Search filter on RecyclerView with Cards. Is it even possible? (hope this helps).
Here is my adapter class
//Filter class
}
//Activity class
In OnQueryTextChangeListener() method use your adapter. I have casted it to fragment as my adpter is in fragment. You can use the adapter directly if its in your activity class.
With Android Architecture Components through the use of LiveData this can be easily implemented with any type of Adapter. You simply have to do the following steps:
1. Setup your data to return from the Room Database as LiveData as in the example below:
2. Create a ViewModel object to update your data live through a method that will connect your DAO and your UI
3. Call your data from the ViewModel on the fly by passing in the query through onQueryTextListener as below:
Inside
onCreateOptionsMenu
set your listener as followsSetup your query listener somewhere in your SearchActivity class as follows
Note: Steps (1.) and (2.) are standard AAC ViewModel and DAO implementation, the only real "magic" going on here is in the OnQueryTextListener which will update the results of your list dynamically as the query text changes.
If you need more clarification on the matter please don't hesitate to ask. I hope this helped :).
Introduction
Since it is not really clear from your question what exactly you are having trouble with, I wrote up this quick walkthrough about how to implement this feature; if you still have questions feel free to ask.
I have a working example of everything I am talking about here in this GitHub Repository.
If you want to know more about the example project visit the project homepage.
In any case the result should looks something like this:
If you first want to play around with the demo app you can install it from the Play Store:
Anyway lets get started.
Setting up the
SearchView
In the folder
res/menu
create a new file calledmain_menu.xml
. In it add an item and set theactionViewClass
toandroid.support.v7.widget.SearchView
. Since you are using the support library you have to use the namespace of the support library to set theactionViewClass
attribute. Your xml file should look something like this:In your
Fragment
orActivity
you have to inflate this menu xml like usual, then you can look for theMenuItem
which contains theSearchView
and implement theOnQueryTextListener
which we are going to use to listen for changes to the text entered into theSearchView
:And now the
SearchView
is ready to be used. We will implement the filter logic later on inonQueryTextChange()
once we are finished implementing theAdapter
.Setting up the
Adapter
First and foremost this is the model class I am going to use for this example:
It's just your basic model which will display a text in the
RecyclerView
. This is the layout I am going to use to display the text:As you can see I use Data Binding. If you have never worked with data binding before don't be discouraged! It's very simple and powerful, however I can't explain how it works in the scope of this answer.
This is the
ViewHolder
for theExampleModel
class:Again nothing special. It just uses data binding to bind the model class to this layout as we have defined in the layout xml above.
Now we can finally come to the really interesting part: Writing the Adapter. I am going to skip over the basic implementation of the
Adapter
and am instead going to concentrate on the parts which are relevant for this answer.But first there is one thing we have to talk about: The
SortedList
class.SortedList
The
SortedList
is a completely amazing tool which is part of theRecyclerView
library. It takes care of notifying theAdapter
about changes to the data set and does so it a very efficient way. The only thing it requires you to do is specify an order of the elements. You need to do that by implementing acompare()
method which compares two elements in theSortedList
just like aComparator
. But instead of sorting aList
it is used to sort the items in theRecyclerView
!The
SortedList
interacts with theAdapter
through aCallback
class which you have to implement:In the methods at the top of the callback like
onMoved
,onInserted
, etc. you have to call the equivalent notify method of yourAdapter
. The three methods at the bottomcompare
,areContentsTheSame
andareItemsTheSame
you have to implement according to what kind of objects you want to display and in what order these objects should appear on the screen.Let's go through these methods one by one:
This is the
compare()
method I talked about earlier. In this example I am just passing the call to aComparator
which compares the two models. If you want the items to appear in alphabetical order on the screen. This comparator might look like this:Now let's take a look at the next method:
The purpose of this method is to determine if the content of a model has changed. The
SortedList
uses this to determine if a change event needs to be invoked - in other words if theRecyclerView
should crossfade the old and new version. If you model classes have a correctequals()
andhashCode()
implementation you can usually just implement it like above. If we add anequals()
andhashCode()
implementation to theExampleModel
class it should look something like this:Quick side note: Most IDE's like Android Studio, IntelliJ and Eclipse have functionality to generate
equals()
andhashCode()
implementations for you at the press of a button! So you don't have to implement them yourself. Look up on the internet how it works in your IDE!Now let's take a look at the last method:
The
SortedList
uses this method to check if two items refer to the same thing. In simplest terms (without explaining how theSortedList
works) this is used to determine if an object is already contained in theList
and if either an add, move or change animation needs to be played. If your models have an id you would usually compare just the id in this method. If they don't you need to figure out some other way to check this, but however you end up implementing this depends on your specific app. Usually it is the simplest option to give all models an id - that could for example be the primary key field if you are querying the data from a database.With the
SortedList.Callback
correctly implemented we can create an instance of theSortedList
:As the first parameter in the constructor of the
SortedList
you need to pass the class of your models. The other parameter is just theSortedList.Callback
we defined above.Now let's get down to business: If we implement the
Adapter
with aSortedList
it should look something like this:The
Comparator
used to sort the item is passed in through the constructor so we can use the sameAdapter
even if the items are supposed to be displayed in a different order.Now we are almost done! But we first need a way to add or remove items to the
Adapter
. For this purpose we can add methods to theAdapter
which allow us to add and remove items to theSortedList
:We don't need to call any notify methods here because the
SortedList
already does this for through theSortedList.Callback
! Aside from that the implementation of these methods is pretty straight forward with one exception: the remove method which removes aList
of models. Since theSortedList
has only one remove method which can remove a single object we need to loop over the list and remove the models one by one. CallingbeginBatchedUpdates()
at the beginning batches all the changes we are going to make to theSortedList
together and improves performance. When we callendBatchedUpdates()
theRecyclerView
is notified about all the changes at once.Additionally what you have to understand is that if you add an object to the
SortedList
and it is already in theSortedList
it won't be added again. Instead theSortedList
uses theareContentsTheSame()
method to figure out if the object has changed - and if it has the item in theRecyclerView
will be updated.Anyway, what I usually prefer is one method which allows me to replace all items in the
RecyclerView
at once. Remove everything which is not in theList
and add all items which are missing from theSortedList
:This method again batches all updates together to increase performance. The first loop is in reverse since removing an item at the start would mess up the indexes of all items that come up after it and this can lead in some instances to problems like data inconsistencies. After that we just add the
List
to theSortedList
usingaddAll()
to add all items which are not already in theSortedList
and - just like I described above - update all items that are already in theSortedList
but have changed.And with that the
Adapter
is complete. The whole thing should look something like this:The only thing missing now is to implement the filtering!
Implementing the filter logic
To implement the filter logic we first have to define a
List
of all possible models. For this example I create aList
ofExampleModel
instances from an array of movies:Nothing special going on here, we just instantiate the
Adapter
and set it to theRecyclerView
. After that we create aList
of models from the movie names in theMOVIES
array. Then we add all the models to theSortedList
.Now we can go back to
onQueryTextChange()
which we defined earlier and start implementing the filter logic:This is again pretty straight forward. We call the method
filter()
and pass in theList
ofExampleModel
s as well as the query string. We then callreplaceAll()
on theAdapter
and pass in the filteredList
returned byfilter()
. We also have to callscrollToPosition(0)
on theRecyclerView
to ensure that the user can always see all items when searching for something. Otherwise theRecyclerView
might stay in a scrolled down position while filtering and subsequently hide a few items. Scrolling to the top ensures a better user experience while searching.The only thing left to do now is to implement
filter()
itself:The first thing we do here is call
toLowerCase()
on the query string. We don't want our search function to be case sensitive and by callingtoLowerCase()
on all strings we compare we can ensure that we return the same results regardless of case. It then just iterates through all the models in theList
we passed into it and checks if the query string is contained in the text of the model. If it is then the model is added to the filteredList
.And that's it! The above code will run on API level 7 and above and starting with API level 11 you get item animations for free!
I realize that this is a very detailed description which probably makes this whole thing seem more complicated than it really is, but there is a way we can generalize this whole problem and make implementing an
Adapter
based on aSortedList
much simpler.Generalizing the problem and simplifying the Adapter
In this section I am not going to go into much detail - partly because I am running up against the character limit for answers on Stack Overflow but also because most of it already explained above - but to summarize the changes: We can implemented a base
Adapter
class which already takes care of dealing with theSortedList
as well as binding models toViewHolder
instances and provides a convenient way to implement anAdapter
based on aSortedList
. For that we have to do two things:ViewModel
interface which all model classes have to implementViewHolder
subclass which defines abind()
method theAdapter
can use to bind models automatically.This allows us to just focus on the content which is supposed to be displayed in the
RecyclerView
by just implementing the models and there correspondingViewHolder
implementations. Using this base class we don't have to worry about the intricate details of theAdapter
and itsSortedList
.SortedListAdapter
Because of the character limit for answers on StackOverflow I can't go through each step of implementing this base class or even add the full source code here, but you can find the full source code of this base class - I called it
SortedListAdapter
- in this GitHub Gist.To make your life simple I have published a library on jCenter which contains the
SortedListAdapter
! If you want to use it then all you need to do is add this dependency to your app's build.gradle file:You can find more information about this library on the library homepage.
Using the SortedListAdapter
To use the
SortedListAdapter
we have to make two changes:Change the
ViewHolder
so that it extendsSortedListAdapter.ViewHolder
. The type parameter should be the model which should be bound to thisViewHolder
- in this caseExampleModel
. You have to bind data to your models inperformBind()
instead ofbind()
.Make sure that all your models implement the
ViewModel
interface:After that we just have to update the
ExampleAdapter
to extendSortedListAdapter
and remove everything we don't need anymore. The type parameter should be the type of model you are working with - in this caseExampleModel
. But if you are working with different types of models then set the type parameter toViewModel
.After that we are done! However one last thing to mention: The
SortedListAdapter
does not have the sameadd()
,remove()
orreplaceAll()
methods our originalExampleAdapter
had. It uses a separateEditor
object to modify the items in the list which can be accessed through theedit()
method. So if you want to remove or add items you have to calledit()
then add and remove the items on thisEditor
instance and once you are done, callcommit()
on it to apply the changes to theSortedList
:All changes you make this way are batched together to increase performance. The
replaceAll()
method we implemented in the chapters above is also present on thisEditor
object:If you forget to call
commit()
then none of your changes will be applied!All you need to do is to add
filter
method inRecyclerView.Adapter
:itemsCopy
is initialized in adapter's constructor likeitemsCopy.addAll(items)
.If you do so, just call
filter
fromOnQueryTextListener
:It's an example from filtering my phonebook by name and phone number.
Following @Shruthi Kamoji in a cleaner way, we can just use a filterable, its meant for that:
The E here is a Generic Type, you can extend it using your class:
Or just change the E to the type you want (
<CustomerModel>
for example)Then from searchView (the widget you can put on menu.xml):
I recommend modify the solution of @Xaver Kapeller with 2 things below to avoid a problem after you cleared the searched text (the filter didn't work anymore) due to the list back of adapter has smaller size than filter list and the IndexOutOfBoundsException happened. So the code need to modify as below
And modify also in moveItem functionality
Hope that It could help you!