Filter items in a nested RecyclerView using a String

Viewed 169

To preface - my goal is to be able to search for an item in a NestedRecyclerView using a field.

In my app, I have a HomeFragment. In said fragment, I have a recycler view(parent recyclerview), which, in turn, holds a RecyclerView(further - child or nested recyclerView). Here are the adapters for both the parent and child recyclerviews respectively.

public class HomeFragmentRvParentAdapter extends RecyclerView.Adapter<HomeFragmentRvParentAdapter.ParentRvViewHolder> {
    private Context context;
    private Map<String, ArrayList<Product>> products;
    private FragmentChangeCallback fragmentChangeCallback;
    ArrayList<String> categories;
    public HomeFragmentRvParentAdapter(Context context, Map<String, ArrayList<Product>> products, FragmentChangeCallback callback){
        this.context = context;
        this.fragmentChangeCallback = callback;
        this.products = products;
    }

    @NonNull
    @Override
    public ParentRvViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.home_fragment_recyclerview_item_parent, parent, false);
        categories = new ArrayList<>(products.keySet());
        return new ParentRvViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ParentRvViewHolder holder, int position) {
        holder.itemCategoryName.setText(categories.get(position));
        setChildAdapter(holder.itemChildRecyclerView, products.get(categories.get(position)));
    }

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

    public class ParentRvViewHolder extends RecyclerView.ViewHolder {
        TextView itemCategoryName;
        RecyclerView itemChildRecyclerView;
        public ParentRvViewHolder(@NonNull View itemView) {
            super(itemView);
            itemCategoryName = itemView.findViewById(R.id.category_name);
            itemChildRecyclerView = itemView.findViewById(R.id.child_recycler);
        }
    }
private void setChildAdapter(RecyclerView recyclerView, ArrayList<Product> products){
        HomeFragmentRvChildAdapter adapter = new HomeFragmentRvChildAdapter(context ,products, fragmentChangeCallback);
        recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
        recyclerView.setAdapter(adapter);

    }
}
public class HomeFragmentRvChildAdapter extends RecyclerView.Adapter<HomeFragmentRvChildAdapter.ChildViewHolder> {
    ArrayList<Product> products;
    Context context;
    FragmentChangeCallback fragmentChangeCallback;
    public HomeFragmentRvChildAdapter(Context context, ArrayList<Product>products, FragmentChangeCallback callback){
        this.products = products;
        this.context = context;
        this.fragmentChangeCallback = callback;
    }

    @NonNull
    @Override
    public ChildViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.home_fragment_recyclerview_item_child, parent, false);
        return new ChildViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ChildViewHolder holder, int position) {
        Glide.with(context).load(products.get(position).getIcon()).into(holder.iconView);
        holder.name.setText(products.get(position).getName());
        holder.description.setText(products.get(position).getDescription());
        holder.itemView.setOnClickListener(view -> {
            fragmentChangeCallback.fragmentChange(products.get(position).getPackageName());
        });
    }

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

    public class ChildViewHolder extends RecyclerView.ViewHolder {
        public ImageView iconView;
        public TextView name;
        public TextView description;
        public CardView cardView;
        public ChildViewHolder(@NonNull View itemView) {
            super(itemView);
            cardView = itemView.findViewById(R.id.cardView);
            iconView = itemView.findViewById(R.id.hf_item_icon_image);
            name = itemView.findViewById(R.id.app_title);
            description = itemView.findViewById(R.id.app_description);
        }
    }
}

public class Product{
    private String name;
    private String icon;
    private String category;
    private String description;
}

^fields of the Product class I use to fill the child recycler.

The way I pass data into this is - in my HomeFragmentViewModel, I get the data. I then categorize that data into a HashMap<String, ArrayList>, where the key is the category. I then pass said map to my ParentRecyclerViewAdapter, which sets the category_title to the value of the key, and passes the linked ArrayList to the ChildRecyclerViewAdapter, that then divides all of it to display.

My goal is, as stated prior, to be able to search for items in the child recycler using the EditText that is in the HomeFragment. I've tried just getting the entirety of my database, and sorting through that with the name of the item, and then displaying that in the recyclerView, but I thought there might be a more elegant way.

The only better way I can think is to maybe somehow just apply View.GONE to the Views whose names do not contain the string I get for searching. That would require the names of the child items, which I'm not sure how to get though.

1 Answers

The way I solved this is: Created a custom CardView class, that has a field for the name.

public class CustomCardView extends CardView {
    private String name;
    private String description;
    private String icon;
    public CustomCardView(@NonNull Context context) {
        super(context);
    }

    public CustomCardView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomCardView, 0,0);
        try{
            name = a.getString(R.styleable.CustomCardView_appName);
            description = a.getString(R.styleable.CustomCardView_descriptionText);
        }finally {
            a.recycle();
        }
    }

    public CustomCardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomCardView, 0,0);
        try{
            name = a.getString(R.styleable.CustomCardView_appName);
            description = a.getString(R.styleable.CustomCardView_descriptionText);
        }finally {
            a.recycle();
        }
    }

    public String getName(){
        return name;
    }

    public String getDescription(){
        return description;
    }

    public void setName(String name){
        this.name = name;
        invalidate();
        requestLayout();
    }

    public void setDescription(String description){
        this.description = description;
        invalidate();
        requestLayout();
    }
}

Then, I created a static ArrayList, that would hold all the child objects.

public class RecyclerViewItemHolder {
    public static ArrayList<CustomCardView> childViews = new ArrayList<>();
    private static RecyclerViewItemHolder instance = null;
    private RecyclerViewItemHolder(){

    }
    public static RecyclerViewItemHolder getInstance(){
        if (instance == null){
            instance = new RecyclerViewItemHolder();
        }
        return instance;
    }
    public static ArrayList<CustomCardView> getChildViews(){
        return childViews;
    }
}

In the onBindViewHolder of the ChildRecyclerViewAdapter I added every CustomCardView to that ArrayList.

@Override
    public void onBindViewHolder(@NonNull ChildViewHolder holder, int position) {
RecyclerViewItemHolder.getInstance().childViews.add(holder.cardView);
}

Then, I accessed the ArrayList from the afterTextChanged method of the TextWatcher I set to the EditText. I used a for loop to check if the name of the cardView contains the search query, and applied View.GONE if it didn't.

private void initListeners() {
        binding.searchView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                for (CustomCardView cardView: RecyclerViewItemHolder.getChildViews()) {
                    if (!cardView.getName().contains(editable.toString())) {
                        cardView.setVisibility(View.GONE);
                    }else{
                        cardView.setVisibility(View.VISIBLE);
                    }
                }
            }
        });

The only issue left is that the empty categories don't disappear, and just stay there empty.

Now, this is probably not the best way to do it, so I'll still appreciate any answers, or comments on how to improve on this solution.

Related