Rob N
Rob N

Reputation: 16399

RecyclerView's removed row still gets click events?

I have a RecyclerView showing a list of items. The ViewHolder for each rows contains a delete button, to remove the row. If I remove the last row and tap very fast on the disappearing row, I can crash my app because the second tap event is delivered to the removed row. I'm surprised that android delivers the second event. Before I try adding something like boolean isDeleted to my ViewHolder subclass, I'm wondering: am I doing something else wrong to get in this situation?

class MyAdapter extends RecyclerView.Adapter<MyViewHolder> 
                implements ItemTouchHelperAdapter {

    List<Segment> segments;
    MyAdapter(List<Segment> objs) {
        this.segments = objs;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
                .inflate(R.layout.segment_edit_row, parent, false);
        MyViewHolder vh = new MyViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Segment seg = segments.get(position);
        holder.textView.setText(seg.getTitle());

        holder.dragHandle.setOnTouchListener((v, event) -> {
            if (MotionEventCompat.getActionMasked(event) ==
                    MotionEvent.ACTION_DOWN) {
                onStartDrag(holder);
            }
            return false;
        });
        holder.deleteButton.setOnClickListener(v -> {                
            segments.remove(position);
            notifyItemRemoved(position);
        });

        View.OnClickListener editExerciseListener = v -> {                
            Segment segment = segments.get(position);
            startEditSegmentActivity(segment, position);
        };

        holder.textView.setOnClickListener(editExerciseListener);
        holder.arrow.setOnClickListener(editExerciseListener);
    }

The deleteButton handler runs first, and then the editExerciseListener, with the position that is now out of bounds.

Update

Several people have suggested I call notifyDataSetChanged. The Android docs specifically recommend not to do that if you can describe your change with a call to notifyItemRemoved instead.

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#notifyDataSetChanged()

Are those docs wrong?

Upvotes: 0

Views: 165

Answers (4)

shashank kushwaha
shashank kushwaha

Reputation: 23

I ran into similar situation a while ago. The app crashed when I clicked recyclerview item while it was being removed and was undergoing removal animation (takes less than a second). The duration for which your app is vulnerable to a potential crash is very small in this situation.

WHAT I DID :

I logged the value of position (holder.getAdapterPosition()) when the app crashed. Surprisingly, every time the crash occurred, the value was -1. Eventually, I wrote a custom version of notifyItemRemoved. Here it is:

private void notifyItemRemovedModified(int pos_removed){
    if(pos_removed!=-1){
        // your list is your list
        yourList.remove(pos_removed);
        notifyItemRemoved(pos_removed);
    }
}

Upvotes: 0

Tariqul Islam
Tariqul Islam

Reputation: 704

First call notifyItemRemoved(position);

then call segments.remove(position);

Upvotes: 0

Muazzam A.
Muazzam A.

Reputation: 645

Update your code as:

    View.OnClickListener editExerciseListener = v -> {  

            if(position >= segments.size()){
              //index not exists
            }else{
              // index exists
            Segment segment = segments.get(position);
            startEditSegmentActivity(segment, position);
            }              

        };

Upvotes: 0

Ravi Mishra
Ravi Mishra

Reputation: 167

You have to reload recyclerview every you perform delete operation: You can do by adding the following line:

yourAdapter.notifyDataSetChanged();

Upvotes: 2

Related Questions