Search code examples
androidandroid-recyclerviewsearchview

SearchView with RecyclerView


I've been trying to add SearchView to my RecyclerView for some time and have referenced these posts here, here and here. At this point, I'm sure the answer is staring me right in the face.

However, I'm not sure how to implement the SearchView when for example, using the following:

private static final List<DataModel> getDummyData(){
    List<DataModel> dummyDataList = new ArrayList<>();

    dummyDataList.add(new DataModel("Alphabet", "Sub Alphabet"));
    dummyDataList.add(new DataModel("Banana", "Sub Banana"));
    dummyDataList.add(new DataModel("Captain", "Sub Captain"));
    dummyDataList.add(new DataModel("Donut", "Sub Donut"));
    dummyDataList.add(new DataModel("Elephant", "Sub Elephant"));
    dummyDataList.add(new DataModel("Fox", "Sub Fox"));
    dummyDataList.add(new DataModel("Giraffe", "Sub Giraffe"));
    dummyDataList.add(new DataModel("Hippo", "Sub Hippo"));
    dummyDataList.add(new DataModel("Iguana", "Sub Iguana"));
    dummyDataList.add(new DataModel("Jumanji", "Sub Jumanji"));

    return dummyDataList;
    }

Here's my current setup (following this tutorial) which uses Locale.getISOCountries(); and searches through the names of countries.

MainActivityFragment:

public class MainActivityFragment extends Fragment implements    SearchView.OnQueryTextListener {

private RecyclerView mRecyclerView;
private List<DataModel> mDataModel;
private RVAdapter adapter;

public MainActivityFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.tab_one_fragment, container, false);

    mRecyclerView = (RecyclerView)view.findViewById(R.id.recyclerview);
    LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    mRecyclerView.setLayoutManager(layoutManager);
    return view;
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    setHasOptionsMenu(true);

    String[] locales = Locale.getISOCountries();

    mDataModel = new ArrayList<>();

    for (String countryCode : locales){
        Locale obj = new Locale("", countryCode);
        mDataModel.add(new DataModel(obj.getDisplayCountry(), obj.getISO3Country()));
    }
    adapter = new RVAdapter(mDataModel);
    mRecyclerView.setAdapter(adapter);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.menu_main, menu);

    final MenuItem item = menu.findItem(R.id.action_search);
    final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
    searchView.setOnQueryTextListener(this);

    MenuItemCompat.setOnActionExpandListener(item,
            new MenuItemCompat.OnActionExpandListener() {
                @Override
                public boolean onMenuItemActionExpand(MenuItem item) {
                    return true;
                }
                @Override
                public boolean onMenuItemActionCollapse(MenuItem item) {
                    // Do something when collapsed
                    adapter.setFilter(mDataModel);
                    return true; 
                }
            });
}

@Override
public boolean onQueryTextChange(String newText) {
    final List<DataModel> filteredModeList = filter(mDataModel, newText);
    adapter.setFilter(filteredModeList);
    return true;
}

@Override
public boolean onQueryTextSubmit(String query) {
    return false;
}

private List<DataModel>filter(List<DataModel> models, String query){
    query = query.toLowerCase();

    final List<DataModel> filteredModeList = new ArrayList<>();
    for (DataModel model : models){
        final String text = model.getName().toLowerCase();
        if (text.contains(query)){
            filteredModeList.add(model);
        }
    }
    return filteredModeList;
    }
}

And my Adapter-RVAdapter.java:

public class RVAdapter extends RecyclerView.Adapter<ItemViewHolder> {

private List<DataModel> mDataModel;

public RVAdapter(List<DataModel> mDataModel){
    this.mDataModel = mDataModel;
}

@Override
public void onBindViewHolder(ItemViewHolder itemViewHolder, int position) {
    final DataModel model = mDataModel.get(position);
    itemViewHolder.bind(model);
}

@Override
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int position){
    View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, viewGroup, false);
    return new ItemViewHolder(view);
}

@Override
public int getItemCount() {
    return mDataModel.size();
}

public void setFilter(List<DataModel> dataModels){
    mDataModel = new ArrayList<>();
    mDataModel.addAll(dataModels);
    notifyDataSetChanged();
    }
}

Here's the ItemViewHolder.java:

public class ItemViewHolder extends RecyclerView.ViewHolder {

public TextView name_TextView;
public TextView subName_TextView;

public ItemViewHolder(View itemView){
    super(itemView);

    name_TextView = (TextView)itemView.findViewById(R.id.country_name);
    subName_TextView = (TextView)itemView.findViewById(R.id.country_iso);
}

public void bind(DataModel dataModel){
    name_TextView.setText(dataModel.getName());
    subName_TextView.setText(dataModel.getSubName());
    }
}

And finally, DataModel.java:

public class DataModel {

String name;
String subName;

DataModel(String name, String subName){
    this.name = name;
    this.subName = subName;
}

public String getName(){
    return name;
}

public String getSubName(){
    return subName;
    }
}

Any help, suggestions or a point in the right direction would be awesome!


Solution

  • Alright, so after taking a closer look at the post here, (which this answer is heavily based on and couldn't do without), I ended up with the following below:

    Note: I originally tested this out on a project that had a TabLayout, hence some of the tab-related variable and class names. Also, all of my Layouts are the same as those in the post linked above, however using it with TabLayout, CoordinatorLayout/AppBarLayout etc shouldn't be difficult.

    Here is the MainFragment.java

    public class MainFragment extends Fragment implements SearchView.OnQueryTextListener {
    
    public static MainFragment newInstance() {
        return new MainFragment();
    }
    
    RecyclerView mRecyclerView;
    private List<MemberData> tabListItem;
    private RecyclerViewTabsAdapter mAdapter;
    
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
        final View view = inflater.inflate(R.layout.recyclerview, container, false);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
    
        return view;
    }
    
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        setHasOptionsMenu(true);
    
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mRecyclerView.setHasFixedSize(true);
    
        tabListItem = new ArrayList<>();
    
        tabListItem = getTabRowList();
    
        mAdapter = new RecyclerViewTabsAdapter(getActivity(), tabListItem);
        mRecyclerView.setAdapter(mAdapter);
    }
    
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_main, menu);
        final MenuItem item = menu.findItem(R.id.action_search);
        final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
        searchView.setOnQueryTextListener(this);
    }
    
    @Override
    public boolean onQueryTextSubmit(String query) {
        return false;
    }
    
    @Override
    public boolean onQueryTextChange(String newText) {
        final List<MemberData> filteredModelList = filter(tabListItem, newText);
        mAdapter.animateTo(filteredModelList);
        mRecyclerView.scrollToPosition(0);
    
        return true;
    }
    
    private List<MemberData> filter(List<MemberData> datas, String newText) {
        newText = newText.toLowerCase();
    
        final List<MemberData> filteredModelList = new ArrayList<>();
        for (MemberData data : datas) {
            final String text = data.getName().toLowerCase();
            if (text.contains(newText)) {
                filteredModelList.add(data);
            }
        }
        return filteredModelList;
    }
    
    private List<MemberData> getTabRowList() {
        List<MemberData> currentItem = new ArrayList<>();
        currentItem.add(new MemberData("Albert", "Albert Sub", R.drawable.your_drawable));
        currentItem.add(new MemberData("Abby", "Abby Text", R.drawable.your_drawable));
        currentItem.add(new MemberData("Brian", "Brian Text", R.drawable.your_drawable));
        currentItem.add(new MemberData("Chris", "Chris Text", R.drawable.your_drawable));
        currentItem.add(new MemberData("Dante", "Dante Text", R.drawable.your_drawable));
    
    //Add more of your dummy data here
    
    
            return currentItem;
        }
    }
    

    And the Adapter:

    public class RecyclerViewTabsAdapter extends RecyclerView.Adapter<RecyclerViewTabsViewHolder> {
    
    private final LayoutInflater mInflater;
    private final List<MemberData>mItemList;
    
    public RecyclerViewTabsAdapter(Context context, List<MemberData> itemList) {
        mInflater = LayoutInflater.from(context);
        mItemList = new ArrayList<>(itemList);
    
    }
    
    @Override
    public RecyclerViewTabsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view = mInflater.inflate(R.layout.cardview_detail, parent, false);
        return new RecyclerViewTabsViewHolder(view);
    }
    
    @Override
    public void onBindViewHolder(final RecyclerViewTabsViewHolder holder, final int position) {
        final MemberData data = mItemList.get(position);
        holder.bind(data);
        //TODO: Set onClick here
    
    }
    
    @Override
    public int getItemCount() {
        return this.mItemList.size();
    
    }
    
    public void animateTo(List<MemberData> memberDatas) {
        applyAndAnimateRemovals(memberDatas);
        applyAndAnimateAdditions(memberDatas);
        applyAndAnimateMovedItems(memberDatas);
    }
    
    private void applyAndAnimateRemovals(List<MemberData> newDatas) {
        for (int i = mItemList.size() - 1; i >= 0; i--) {
            final MemberData data = mItemList.get(i);
            if (!newDatas.contains(data)) {
                removeItem(i);
            }
        }
    }
    
    private void applyAndAnimateAdditions(List<MemberData> newDatas) {
        for (int i = 0, count = newDatas.size(); i < count; i++) {
            final MemberData data = newDatas.get(i);
            if (!mItemList.contains(data)) {
                addItem(i, data);
            }
        }
    }
    
    private void applyAndAnimateMovedItems(List<MemberData> newDatas) {
        for (int toPosition = newDatas.size() - 1; toPosition >= 0; toPosition--) {
            final MemberData data = newDatas.get(toPosition);
            final int fromPosition = mItemList.indexOf(data);
            if (fromPosition >= 0 && fromPosition != toPosition) {
                moveItem(fromPosition, toPosition);
            }
        }
    }
    
    public MemberData removeItem(int position) {
        final MemberData data = mItemList.remove(position);
        notifyItemRemoved(position);
        return data;
    }
    
    public void addItem(int position, MemberData data) {
        mItemList.add(position, data);
        notifyItemInserted(position);
    }
    
    public void moveItem(int fromPosition, int toPosition) {
        final MemberData data = mItemList.remove(fromPosition);
        mItemList.add(toPosition, data);
        notifyItemMoved(fromPosition, toPosition);
        }
    }
    

    And the ViewHolder

    public class RecyclerViewTabsViewHolder extends RecyclerView.ViewHolder {
    
    CardView cv;
    TextView name;
    TextView subText;
    ImageView memberPhoto;
    
    public RecyclerViewTabsViewHolder(View itemView){
        super(itemView);
        cv = (CardView)itemView.findViewById(R.id.cv);
        name = (TextView)itemView.findViewById(R.id.person_name);
        subText = (TextView)itemView.findViewById(R.id.person_subtext);
        memberPhoto = (ImageView)itemView.findViewById(R.id.person_photo);
    }
    
    public void bind(MemberData data){
        name.setText(data.getName());
        subText.setText(data.getSubText());
        memberPhoto.setImageResource(data.getPhotoId());
        }
    }
    

    And finally, MemberData.java

    public class MemberData {
    
    String name;
    String subText;
    int photoId;
    
    public MemberData(String name, String subText, int photoId){
        this.name = name;
        this.subText = subText;
        this.photoId = photoId;
    }
    
    public String getName(){
        return name;
    }
    
    public String getSubText() {
        return subText;
    }
    
    public int getPhotoId() {
        return photoId;
        }
    }
    

    The only remaining issue (and I'll save this for another post), is the onClick method and getting the correct position after the list has been filtered.

    Hope this helps!