0

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?

Rob N
  • 11,371
  • 11
  • 72
  • 126

4 Answers4

2

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

yourAdapter.notifyDataSetChanged();
Ravi Mishra
  • 74
  • 1
  • 4
  • My call to `notifyItemRemoved` doesn't cut it? I thought that was a more efficient way of telling it exactly what changed, so it didn't have to reload the whole data set. – Rob N Sep 27 '19 at 11:34
  • check this link: [https://stackoverflow.com/questions/31367599/how-to-update-recyclerview-adapter-data] – Ravi Mishra Sep 27 '19 at 11:50
0

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);
            }              

        };
Muazzam A.
  • 529
  • 4
  • 17
0

First call notifyItemRemoved(position);

then call segments.remove(position);

MaartenDev
  • 4,068
  • 5
  • 16
  • 28
Tariqul Islam
  • 621
  • 7
  • 13
0

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);
    }
}