Showing empty state when filtered results are 0 in recycler view

Viewed 1493

I got this searchView which I use to filter recyclerView. So when there is no results for some search query, I want to show textView to user by setting its visibility to VISIBLE. So in searchView listener, I do:

            @Override
            public boolean onQueryTextChange(String s) {
                adapter.getFilter().filter(s);

                if (adapter.getItemCount()<=0){
                    emptyStateConatiner.setVisibility(View.VISIBLE);
                }
                else  emptyStateConatiner.setVisibility(View.GONE);

                return false;
            }

But this doesn't work as I would expect, because when I type non existing search query, it first shows empty recyclerView and only after that, if I type additional letter to that query, I get emptyStateContainer shown.

Does anyone know what is causing this behavior?

Here is adapter code: package com.app.userprofile;

import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.Filter;
import android.widget.Filterable;

import com.app.R;
import com.app.data.webservice.collection_response.Datum;

import java.util.ArrayList;
import java.util.List;

public class BasicUserProfileInfoListAdapter extends RecyclerView.Adapter<BasicUserProfileInfoListAdapter.BasicInfoViewHolder> implements Filterable {

    private List<Datum> offeredChoices = new ArrayList<>();
    private List<Datum> offeredChoicesFiltered = new ArrayList<>();
    private List<Datum> previouslySelectedChoices = new ArrayList<>();


    private Resources resources;
    private ItemClickCallback clickCallback;

    //for single choices
    int mCheckedPostion=-1;

    public BasicUserProfileInfoListAdapter(ItemClickCallback clickCallback){
        this.clickCallback = clickCallback;
    }

    public void setList(List<Datum> datumList) {
        this.offeredChoices = datumList;
        this.offeredChoicesFiltered = datumList;
        notifyDataSetChanged();
    }






    @NonNull
    @Override
    public BasicInfoViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View itemView = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.basic_choice_item, viewGroup, false);
        if (resources == null) {
            resources = itemView.getContext().getApplicationContext().getResources();
        }

        return new BasicInfoViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull final BasicInfoViewHolder basicInfoViewHolder, final int i) {
        final Datum currentData = offeredChoicesFiltered.get(i);

        basicInfoViewHolder.basicInfoCheckBox.setText(currentData.getDesc());
        //TODO if currentData equals some choice in previouslySelectedChoices, than mark it checked and add it to selected choices via interface

       if (clickCallback instanceof MultipleChoiceItemClickCallback && previouslySelectedChoices!=null && !previouslySelectedChoices.isEmpty()){
           boolean shouldCheck=false;
           for (Datum data:previouslySelectedChoices) {
               if ((data.getId().equals(currentData.getId()))/*&& data.getDesc().equals(currentData.getDesc())*/){
                   shouldCheck=true;
                   clickCallback.onRowClicked(currentData,ChoiceClickType.ADD_CHOICE);
               }
           }
           basicInfoViewHolder.basicInfoCheckBox.setChecked(shouldCheck);
       }

        if (clickCallback instanceof SingleChoiceItemClickCallback){
            basicInfoViewHolder.basicInfoCheckBox.setChecked(i == mCheckedPostion);
            if (previouslySelectedChoices!=null && !previouslySelectedChoices.isEmpty()){
                boolean shouldCheck=false;
                for (Datum data:previouslySelectedChoices) {
                    if ((data.getId().equals(currentData.getId()))/*&& data.getDesc().equals(currentData.getDesc())*/){
                        shouldCheck=true;
                        previouslySelectedChoices.clear();//so it wouldn't reselect on notifyDataSetChanged()
                        clickCallback.onRowClicked(currentData,ChoiceClickType.ADD_SINGLE_CHOICE);
                    }

                }

                if (shouldCheck) mCheckedPostion = i;
                //Log.d("slctdwf","choice: "+ currentData.getDesc()+", position: "+mCheckedPostion);
                basicInfoViewHolder.basicInfoCheckBox.setChecked(shouldCheck);
            }


        }
    }

    @Override
    public int getItemCount() {
        if (offeredChoicesFiltered!=null) return offeredChoicesFiltered.size();
        else  return 0;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    private Datum getCurrentRowInfo(int position) {
        return offeredChoicesFiltered.get(position);
    }

    public void setPreviouslySelectedChoices (List<Datum> data) {
        if (data!=null && !data.isEmpty()){
        this.previouslySelectedChoices = data;
        notifyDataSetChanged();
        }
    }

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                String charString = constraint.toString();
                List<Datum> filteredList = new ArrayList<>();
                if (charString.isEmpty()) {
                    filteredList = offeredChoices;
                } else {
                    for (Datum choice : offeredChoices) {

                        if (choice.getDesc().toLowerCase().contains(charString.toLowerCase())) {
                            filteredList.add(choice);
                        }
                    }

                }

                FilterResults filterResults = new FilterResults();
                filterResults.count = filteredList.size();
                filterResults.values = filteredList;
                return filterResults;
            }

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

    class BasicInfoViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private CheckBox basicInfoCheckBox;
        private CardView entireRowCardView;

        BasicInfoViewHolder(View itemView) {
            super(itemView);
            basicInfoCheckBox = itemView.findViewById(R.id.choice_checkbox);
            entireRowCardView = itemView.findViewById(R.id.entire_row_basic_choice);
            entireRowCardView.setOnClickListener(this);
            if (resources == null) {
                resources = itemView.getResources();
            }

        }

        @Override
        public void onClick(View v) {
            Datum currentChoice = getCurrentRowInfo(getAdapterPosition());
            int position = getAdapterPosition();

            if (clickCallback instanceof MultipleChoiceItemClickCallback){
                if (basicInfoCheckBox.isChecked()){
                    basicInfoCheckBox.setChecked(false);
                    clickCallback.onRowClicked(currentChoice, ChoiceClickType.REMOVE_CHOICE);
                }
                else if (!basicInfoCheckBox.isChecked()){
                    basicInfoCheckBox.setChecked(true);
                    clickCallback.onRowClicked(currentChoice, ChoiceClickType.ADD_CHOICE);
                }
            }

            if (clickCallback instanceof SingleChoiceItemClickCallback){
                        if (position == mCheckedPostion) {
                            mCheckedPostion = -1;
                            //Log.d("slctdwf","removed single choice: "+ currentChoice.getDesc());
                            clickCallback.onRowClicked(currentChoice, ChoiceClickType.REMOVE_SINGLE_CHOICE);
                            basicInfoCheckBox.setChecked(false);
                            notifyDataSetChanged();

                        } else {
                            mCheckedPostion = position;
                            //Log.d("slctdwf","added single choice: "+ currentChoice.getDesc());
                            clickCallback.onRowClicked(currentChoice, ChoiceClickType.ADD_SINGLE_CHOICE);
                            basicInfoCheckBox.setChecked(true);
                            notifyDataSetChanged();
                        }


            }

        }
    }



    public interface ItemClickCallback {
        void onRowClicked(Datum choiceData, ChoiceClickType choiceClickType);
    }

    public interface MultipleChoiceItemClickCallback extends ItemClickCallback{
        void onRowClicked(Datum choiceData, ChoiceClickType choiceClickType);
    }

    public interface SingleChoiceItemClickCallback extends ItemClickCallback {
        void onRowClicked(Datum choiceData, ChoiceClickType choiceClickType);
    }

    public enum ChoiceClickType {
        ADD_CHOICE,
        REMOVE_CHOICE,
        ADD_SINGLE_CHOICE,
        REMOVE_SINGLE_CHOICE
    }

}

1 Answers

I think the onQueryChanged callback is the wrong place to control the empty state. You could use a DataSetObserver (documentation) to listen to list changes so you always have the correct size() of matches.

Register an observer that is called when changes happen to the data used by this adapter.

adapter.registerDataSetObserver(new DataSetObserver() {
    @Override public void onChanged() {
        super.onChanged();
        showEmptyStateIfAdapterIsEmpty();
        // access adapter's dataset size here or in that method
    } 
});
Related