Filter recycler view populated by view model's LiveData entries

3k views Asked by At

I am using architecture component view model's LiveData to populate recycler view and want to add a searchview filter but couldn't find any solution. I tried to use filterable interface in the adapter but its also not working, possible because view model doesn't let it change.

my adapter is as-

public class NetworkAdapter extends RecyclerView.Adapter<NetworkAdapter.NetworksViewHolder> implements Filterable {

// Member variable to handle item clicks
final private ItemClickListener mItemClickListener;
// Class variables for the List that holds task data and the Context
private List<NetworkEntry> mNetworkEntries;
private List<NetworkEntry> mFilteredNetworkEntries = new ArrayList<>();
private Context mContext;

/**
 * Constructor for the TaskAdapter that initializes the Context.
 *
 * @param context  the current Context
 * @param listener the ItemClickListener
 */
public NetworkAdapter(Context context, ItemClickListener listener) {
    mContext = context;
    mItemClickListener = listener;
}

/**
 * Called when ViewHolders are created to fill a RecyclerView.
 *
 * @return A new TaskViewHolder that holds the view for each task
 */
@Override
public NetworksViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // Inflate the task_layout to a view
    View view = LayoutInflater.from(mContext)
            .inflate(R.layout.network_list_item, parent, false);

    return new NetworksViewHolder(view);
}

/**
 * Called by the RecyclerView to display data at a specified position in the Cursor.
 *
 * @param holder   The ViewHolder to bind Cursor data to
 * @param position The position of the data in the Cursor
 */
@Override
public void onBindViewHolder(NetworksViewHolder holder, int position) {
    // Determine the values of the wanted data
    holder.onBindData(position);
}

/**
 * Returns the number of items to display.
 */
@Override
public int getItemCount() {
    if (mNetworkEntries == null) {
        return 0;
    }
    return mNetworkEntries.size();
}

/**
 * When data changes, this method updates the list of taskEntries
 * and notifies the adapter to use the new values on it
 */
public void setNetworks(List<NetworkEntry> networkEntries) {
    mNetworkEntries = networkEntries;
    mFilteredNetworkEntries.addAll(mNetworkEntries);
    notifyDataSetChanged();
}



public List<NetworkEntry> getNetworks() {
    return mNetworkEntries;
}

@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            String charString = charSequence.toString();
            if (charString.isEmpty()) {
                mFilteredNetworkEntries = mNetworkEntries;
            } else {
                List<NetworkEntry> filteredList = new ArrayList<>();
                for (NetworkEntry row : mNetworkEntries) {

                    // name match condition. this might differ depending on your requirement
                    // here we are looking for name or phone number match
                    if (row.getNetworkName().toLowerCase().contains(charString.toLowerCase())) {
                        filteredList.add(row);
                    }
                }

                mFilteredNetworkEntries = filteredList;
            }

            FilterResults filterResults = new FilterResults();
            filterResults.values = mFilteredNetworkEntries;
            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            mFilteredNetworkEntries = (ArrayList<NetworkEntry>) filterResults.values;
            notifyDataSetChanged();
        }
    };
}

public interface ItemClickListener {
    void onItemClickListener(NetworkData networkData);
}


public class NetworksViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    ImageView networkLogoImageView;
    TextView networkNameTextView;
    TextView joinTextView;
    TextView commissionTextView;
    TextView minPayTextView;
    TextView payFrequencyTextView;
    TextView offerTextView;
    TextView likeTextView;
    View containerView;

    public NetworksViewHolder(View itemView) {
        super(itemView);
        networkLogoImageView = (ImageView) itemView.findViewById(R.id.iv_logo);
        networkNameTextView = (TextView) itemView.findViewById(R.id.tv_network_name);
        joinTextView = (TextView) itemView.findViewById(R.id.bt_join);
        commissionTextView = (TextView) itemView.findViewById(R.id.iv_commission);
        minPayTextView = (TextView) itemView.findViewById(R.id.tv_min_pay);
        payFrequencyTextView = (TextView) itemView.findViewById(R.id.tv_pay_frequency);
        offerTextView = (TextView) itemView.findViewById(R.id.iv_offers);
        likeTextView = (TextView) itemView.findViewById(R.id.iv_likes);
        containerView = itemView.findViewById(R.id.layoutContainer);

        joinTextView.setOnClickListener(this);
        containerView.setOnClickListener(this);
    }

    public void onBindData(int position) {
        NetworkEntry networkEntry = mNetworkEntries.get(getAdapterPosition());
        Picasso.with(mContext).load(networkEntry.getNetworkImageUrl()).into(networkLogoImageView);
        networkNameTextView.setText(networkEntry.getNetworkName());
        commissionTextView.setText(networkEntry.getNetworkCommission());
        minPayTextView.setText(mContext.getString(R.string.tv_min_pay, networkEntry.getNetworkMinPay()));
        payFrequencyTextView.setText(networkEntry.getNetworkPayFrequency());
        offerTextView.setText(networkEntry.getNetworkOffers());

    }

    @Override
    public void onClick(View view) {
        int adapterPosition = getAdapterPosition();
        NetworkEntry networkEntry = mNetworkEntries.get(adapterPosition);
        if (view.getId() == R.id.bt_join) {
            AppUtilities.openWebPage(mContext, networkEntry.getNetworkJoinUrl());
        } else {

            NetworkData networkData = new NetworkData(networkEntry);
            Log.v(NetworkAdapter.class.getSimpleName(), "Network SNO : " + networkData.getSno());
            mItemClickListener.onItemClickListener(networkData);
        }
    }

}

}

And search view OnQueryTextListener is as -

   @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.search_menu, menu);
    MenuItem searchItem = menu.findItem(R.id.action_search);
    SearchView searchView = (SearchView) searchItem.getActionView();
    searchView.setOnQueryTextListener(this);
    searchView.setQueryHint("Search");

    super.onCreateOptionsMenu(menu, inflater);
}


@Override
public boolean onQueryTextSubmit(String query) {
    adapter.getFilter().filter(query);
    return false;
}

@Override
public boolean onQueryTextChange(String newText) {
    adapter.getFilter().filter(newText);
    return false;
}
1

There are 1 answers

2
Levi Moreira On BEST ANSWER

The problem happens because you should be using the FilteredList in your adapter, not the original list:

public class NetworkAdapter extends RecyclerView.Adapter<NetworkAdapter.NetworksViewHolder> implements Filterable {

// Member variable to handle item clicks
final private ItemClickListener mItemClickListener;
// Class variables for the List that holds task data and the Context
private List<NetworkEntry> mNetworkEntries;
private List<NetworkEntry> mFilteredNetworkEntries = new ArrayList<>();
private Context mContext;

/**
 * Constructor for the TaskAdapter that initializes the Context.
 *
 * @param context  the current Context
 * @param listener the ItemClickListener
 */
public NetworkAdapter(Context context, ItemClickListener listener) {
    mContext = context;
    mItemClickListener = listener;
}

/**
 * Called when ViewHolders are created to fill a RecyclerView.
 *
 * @return A new TaskViewHolder that holds the view for each task
 */
@Override
public NetworksViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // Inflate the task_layout to a view
    View view = LayoutInflater.from(mContext)
            .inflate(R.layout.network_list_item, parent, false);

    return new NetworksViewHolder(view);
}

/**
 * Called by the RecyclerView to display data at a specified position in the Cursor.
 *
 * @param holder   The ViewHolder to bind Cursor data to
 * @param position The position of the data in the Cursor
 */
@Override
public void onBindViewHolder(NetworksViewHolder holder, int position) {
    // Determine the values of the wanted data
    holder.onBindData(position);
}

/**
 * Returns the number of items to display.
 */
@Override
public int getItemCount() {
    if (mFilteredNetworkEntries == null) {
        return 0;
    }
    return mFilteredNetworkEntries.size();
}

/**
 * When data changes, this method updates the list of taskEntries
 * and notifies the adapter to use the new values on it
 */
public void setNetworks(List<NetworkEntry> networkEntries) {
    mNetworkEntries = networkEntries;
    mFilteredNetworkEntries.addAll(mNetworkEntries);
    notifyDataSetChanged();
}



public List<NetworkEntry> getNetworks() {
    return mFilteredNetworkEntries;
}

@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            String charString = charSequence.toString();
            List<NetworkEntry> filteredList = new ArrayList<>();
            if (charString.isEmpty()) {
                filteredList = mNetworkEntries;
            } else {
                for (NetworkEntry row : mNetworkEntries) {

                    // name match condition. this might differ depending on your requirement
                    // here we are looking for name or phone number match
                    if (row.getNetworkName().toLowerCase().contains(charString.toLowerCase())) {
                        filteredList.add(row);
                    }
                }

            }

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

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            mFilteredNetworkEntries = (ArrayList<NetworkEntry>) filterResults.values;
            notifyDataSetChanged();
        }
    };
}

public interface ItemClickListener {
    void onItemClickListener(NetworkData networkData);
}


public class NetworksViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    ImageView networkLogoImageView;
    TextView networkNameTextView;
    TextView joinTextView;
    TextView commissionTextView;
    TextView minPayTextView;
    TextView payFrequencyTextView;
    TextView offerTextView;
    TextView likeTextView;
    View containerView;

    public NetworksViewHolder(View itemView) {
        super(itemView);
        networkLogoImageView = (ImageView) itemView.findViewById(R.id.iv_logo);
        networkNameTextView = (TextView) itemView.findViewById(R.id.tv_network_name);
        joinTextView = (TextView) itemView.findViewById(R.id.bt_join);
        commissionTextView = (TextView) itemView.findViewById(R.id.iv_commission);
        minPayTextView = (TextView) itemView.findViewById(R.id.tv_min_pay);
        payFrequencyTextView = (TextView) itemView.findViewById(R.id.tv_pay_frequency);
        offerTextView = (TextView) itemView.findViewById(R.id.iv_offers);
        likeTextView = (TextView) itemView.findViewById(R.id.iv_likes);
        containerView = itemView.findViewById(R.id.layoutContainer);

        joinTextView.setOnClickListener(this);
        containerView.setOnClickListener(this);
    }

    public void onBindData(int position) {
        NetworkEntry networkEntry = mFilteredNetworkEntries.get(getAdapterPosition());
        Picasso.with(mContext).load(networkEntry.getNetworkImageUrl()).into(networkLogoImageView);
        networkNameTextView.setText(networkEntry.getNetworkName());
        commissionTextView.setText(networkEntry.getNetworkCommission());
        minPayTextView.setText(mContext.getString(R.string.tv_min_pay, networkEntry.getNetworkMinPay()));
        payFrequencyTextView.setText(networkEntry.getNetworkPayFrequency());
        offerTextView.setText(networkEntry.getNetworkOffers());

    }

    @Override
    public void onClick(View view) {
        int adapterPosition = getAdapterPosition();
        NetworkEntry networkEntry = mFilteredNetworkEntries.get(adapterPosition);
        if (view.getId() == R.id.bt_join) {
            AppUtilities.openWebPage(mContext, networkEntry.getNetworkJoinUrl());
        } else {

            NetworkData networkData = new NetworkData(networkEntry);
            Log.v(NetworkAdapter.class.getSimpleName(), "Network SNO : " + networkData.getSno());
            mItemClickListener.onItemClickListener(networkData);
        }
    }

}
}

The idea is that in your adapter you use the filtered list, the original list is in the adapter only to serve the purpose of being filtered by the performFiltering method.