XCaMeLX
XCaMeLX

Reputation: 11

How to implement Search Filter with RecyclerView

I followed some youtube tutorial about SQLite and RecyclerView (https://www.youtube.com/watch?v=VQKq9RHMS_0&ab_channel=Stevdza-San) I completed all the things and customize it as I need to my app and everything work fine.

Now all I want to do is add Search Button at the top (Action Bar) that i can search items from my RecyclerView.

So i was try to figure out how to implement it, but I struggle with the filter and all those things. Every tutorials that I saw about this work with List (E) while my CustomeAdpter geting ArrayList and I can't undrstand how to make it work with the ArrayList.

So if some one can help me with that and give me some little guidance I will be grateful

EDIT

I added the getFilter() function in my adapter, but when I try to search something nothing happened in RecyclerView.

I am adding my code: MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    activity = MainActivity.this;
    noClubsImage = findViewById(R.id.noClubsImage);
    noClubsText = findViewById(R.id.noClubsText);
    recyclerView = findViewById(R.id.recyclerView);
    add_button = findViewById(R.id.add_button);

    add_button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, AddActivity.class);
            activity.startActivityForResult(intent,1);
        }
    });

    myDB = new MyDatabaseHelper(MainActivity.this);
    club_id = new ArrayList<>();
    club_name = new ArrayList<>();
    join_date = new ArrayList<>();
    expire_date = new ArrayList<>();

    storeDataInArrays();


    customAdapter = new CustomAdapter(MainActivity.this, this, club_id, club_name, join_date, expire_date);
    recyclerView.setAdapter(customAdapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
}

. . . EDIT***

    @Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main_menu, menu);
    MenuItem item = menu.findItem(R.id.action_search);
    SearchView searchView = (SearchView) item.getActionView();
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            customAdapter.getFilter().filter(query);
            return false;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            customAdapter.getFilter().filter(newText);
            return true;
        }
    });
    return super.onCreateOptionsMenu(menu);

}
}

And this is my Adapter:

I added Try & catch function in onBindViewHolder becuase it's crash if not and get this error: "java.lang.IndexOutOfBoundsException: Index: 1, Size: 1" After adding it, I can search but something very weird, upload some pictures: When I start the app Start searching "Yasha" -> it change the two rows

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


private Context context;
private Activity activity;
private ArrayList club_id, club_name, join_date,expire_date,list,originalList;
int position;
Animation translate_anim;


CustomAdapter(Activity activity, Context context, ArrayList club_id, ArrayList club_name, ArrayList join_date, ArrayList expire_date){


    this.activity = activity;
    this.context = context;
    this.club_id = club_id;
    this.club_name = club_name;
    this.join_date = join_date;
    this.expire_date = expire_date;

    this.list = club_name;
    this.originalList = club_name;

}



@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(context);
    View view = inflater.inflate(R.layout.my_row,parent,false);
    return new MyViewHolder(view);
}

public Filter getFilter(){
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            ArrayList filteredResults = null;
            if (constraint.length() == 0) {
                filteredResults = club_name;
            } else {
                filteredResults = getFilteredResults(constraint.toString().toLowerCase());
            }

            FilterResults results = new FilterResults();
            results.values = filteredResults;
            Log.d("Test",filteredResults.toString());
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            club_name = (ArrayList<String>) results.values;
            notifyDataSetChanged();
        }
    };
}

protected ArrayList getFilteredResults(String constraint) {
    ArrayList results = new ArrayList<>();
    for (Object item : originalList) {
        if (item.toString().toLowerCase().contains(constraint)) {
            results.add(item.toString());
        }
    }
    //Log.d("Test",results.toString());
    return results;
}

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {

    try{
        this.position = position;
        holder.club_name_txt.setText(String.valueOf(club_name.get(position)));
        holder.join_txt.setText(String.valueOf(join_date.get(position)));
        holder.expire_txt.setText(String.valueOf(expire_date.get(position)));
        holder.mainLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(context,UpdateActivity.class);
                intent.putExtra("club_id",String.valueOf(club_id.get(position)));
                intent.putExtra("club_name",String.valueOf(club_name.get(position)));
                intent.putExtra("join_date",String.valueOf(join_date.get(position)));
                intent.putExtra("expire_date",String.valueOf(expire_date.get(position)));
                activity.startActivityForResult(intent,1);

            }
        });
    }catch(Exception ex) {
        Log.e("TAG", "EXCEPTION CAUGHT WHILE EXECUTING DATABASE TRANSACTION");
        ex.printStackTrace();
    }
}

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

public class MyViewHolder extends RecyclerView.ViewHolder {

    LinearLayout mainLayout;
    TextView club_id_txt, club_name_txt, join_txt,expire_txt;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);

        club_name_txt = itemView.findViewById(R.id.club_name_txt);
        join_txt = itemView.findViewById(R.id.jDate_txt);
        expire_txt = itemView.findViewById(R.id.eDate_txt);
        mainLayout = itemView.findViewById(R.id.mainLayout);
        translate_anim = AnimationUtils.loadAnimation(context, R.anim.translate_anim);
        mainLayout.setAnimation(translate_anim);
    }
 }}

Upvotes: 1

Views: 551

Answers (1)

snachmsm
snachmsm

Reputation: 19223

you have customAdapter.getFilter(); line in both methods of OnQueryTextListener and it returns filter for you. thats all what you have requested, you've never passed a String to this filter. this line should look like this

customAdapter.getFilter().filter(query); // or newText, depend on OnQueryTextListener method

edit due to comments:

remove this try{}catch() and fix your exception. this method is reliable and only your mistake can lead to crash.

you are filtering and publishing only one of arrays (club_name = (ArrayList<String>) results.values;), but still you are informing adapter that list have number of items equal to club_id.size(); in getItemCount. thats why you are getting IndexOutOfBoundsException - club_id is untouched and have multiple items, even after filtering when club_name have only few or one item inside. you should filter all four arrays, in fact this should be one array with custom object carrying all four params

some tutorial for fixing:

keep your data in some additional arrays which will stay untouched and keep all data even after filtering. for start make "original" and "working" arrays

CustomAdapter(Activity activity, Context context, ArrayList club_id, ArrayList club_name, ArrayList join_date, ArrayList expire_date){
    this.activity = activity;
    this.context = context;

    this.club_id_org = club_id;
    this.club_name_org = club_name;
    this.join_date_org = join_date;
    this.expire_date_org = expire_date;

    this.club_id = new ArrayList<>();
    this.club_id.addAll(this.club_id_org);
    this.club_name = new ArrayList<>();
    this.club_name.addAll(this.club_name_org);
    this.join_date = new ArrayList<>();
    this.join_date.addAll(this.join_date_org);
    this.expire_date = new ArrayList<>();
    this.expire_date.addAll(this.expire_date_org);
}

note how much duplicated code we have in here, thats why this should be one ArrayList with some custom object... but nvm, lets stay (for now) with four arrays

dont use this construction: this.list = club_name; - this makes list and club_name are same arrays (objects), when you remove some item from one then it will be removed from second (in fact same) array, and we need here two separated arrays (x4)

now all arrays with _org suffix are carrying whole lists, but adapter should work on "working" arrays, these without _org, like you have currently in code

now filtering: in performFiltering you should create, again, four temporary arrays and fill them with filtered items. for filtering iterate through _org arrays, these with all items

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        ArrayList club_id_temp = new Arraylist<>(),
                  club_name_temp = new Arraylist<>(),
                  join_date_temp = new Arraylist<>(),
                  expire_date_temp = new Arraylist<>();
        if (constraint.length() == 0) {
            club_id_temp.addAll(club_id_org);
            club_name_temp.addAll(club_name_org);
            join_date_temp.addAll(join_date_org);
            expire_date_temp.addAll(expire_date_org);
        } else {
            for(int i=0; i<club_name_org.size(); i++){
                if(club_name_org.get(i).toLowerCase().contains(constraint.toLowerCase())){
                    club_id_temp.add(club_id_org.get(i));
                    club_name_temp.add(club_name_org.get(i));
                    join_date_temp.add(join_date_org.get(i));
                    expire_date_temp.add(expire_date_org.get(i));
                }
            }
        }

        FilterResults results = new FilterResults();
        results.values = ...

and now we have a small problem... results.values can carry only one object, e.g. one ArrayList and you have four... lets make come class carrying all four for your purposes, declare such e.g. on the bottom of adapter (just before last bracket closing whole adapter })

public static class TempArrays{
    public ArrayList club_id, club_name, join_date, expire_date;
}

and pack all four filtered arrays into:

        FilterResults results = new FilterResults();
        TempArrays ta = new TempArrays();
        ta.club_id = club_id_temp;
        ta.club_name = club_name_temp;
        ta.join_date = join_date_temp;
        ta.expire_date = expire_date_temp;
        results.values = ta;
        return results;
    }

and unpack this construction in publishResults:

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        TempArrays ta = (TempArrays) results.values;
        club_id = ta.club_id;
        club_name = ta.club_name;
        join_date = ta.join_date;
        expire_date = ta.expire_date;
        notifyDataSetChanged();
    }

again: four arrays isn't proper way, look how much duplicated lines we have here... + some small workaround for passing all four after filtering instead of one... in fact there should be some ClubModel class, similar to this

public static class ClubModel{
    public String club_id, club_name, join_date, expire_date;
}

and then you may work on one array with ClubModel items, instead of four arrays. check out POJO definition

Upvotes: 1

Related Questions