I have a RecyclerView
working with FirestoreRecyclerAdapter
in my app. The ViewHolders
implement an expandable 'quick actions' menu that expands on tapping the item, and the ideal behavior is to have only one item expanded at a time. Expanding one item should collapse all other items in the adapter.
When there are few enough items to fit them all on screen, this works fine. But if the list extends off-screen, when I try to iterate over the adapter's items, I get a NullPointerException
thrown when the detached or off-screen ViewHolders try to call getParent()
.
How can I iterate over all ViewHolders in the adapter, or alternatively, only iterate over visible ones?
Relevant adapter code:
@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder,
int position, @NonNull Brew brew) {
// Bind Views
holder.bind(brew);
// Set expander ClickListener
holder.card.setOnClickListener(v -> {
for (int i = 0; i < mAdapter.getItemCount(); i++) {
if (i != position) {
// ERROR THROWN HERE
BrewViewHolder vh = ((BrewViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(i)));
vh.expanded = false;
}
}
holder.expanded = !holder.expanded;
notifyItemChanged(position);
});
}
Relevant ViewHolder code:
public class BrewViewHolder extends RecyclerView.ViewHolder {
// Expanded state
public boolean expanded = false;
...
public BrewViewHolder(@NonNull final View itemView) {
super(itemView);
...
// Set visibility of quick actions based on expanded state
quickActions.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
}
Thanks in advance!
EDIT: As Gilberto pointed out, this is very expensive. Went with a version of Andrew's solution:
private int expandedItemIndex = -1;
@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder, int position,
@NonNull Brew brew) {
// Bind Views
holder.bind(brew);
// Set expander ClickListener
holder.card.setOnClickListener(v -> {
if (position == expandedItemIndex) {
holder.expanded = false;
expandedItemIndex = -1;
} else {
holder.expanded = true;
if (expandedItemIndex != -1) {
BrewViewHolder otherHolder = ((BrewViewHolder) recyclerView.getChildViewHolder(
recyclerView.getChildAt(expandedItemIndex)));
otherHolder.expanded = false;
}
expandedItemIndex = position;
}
notifyItemRangeChanged(0, recyclerView.getChildCount());
});
}
You should not iterate all views. You need just save index of expanded view and call notifyDataSetChanged
or notifyItemChanged
.
private int expandedItemIndex = -1;
@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder,
final int position, @NonNull Brew brew) {
// Bind Views
holder.bind(brew);
// Set expander ClickListener
holder.card.setOnClickListener(v -> {
if (position == expandedItemIndex) {
notifyItemChanged(position);
expandedItemIndex = -1;
} else {
if (expandedItemIndex != -1) {
notifyItemChanged(expandedItemIndex);
}
expandedItemIndex = position;
notifyItemChanged(position);
}
});
if (position == expandedItemIndex) {
// Expand
holder.quickActions.setVisibility(View.VISIBLE);
} else {
// Collapse
holder.quickActions.setVisibility(View.GONE);
}
}