93

I am creating a list of cards to display using the RecyclerView, where each card has a button to remove that card from the list.

When i use notifyItemRemoved() to remove the card in the RecyclerView, it removes the item and animates fine but the data in the list is not updated correctly.

If instead of that, i switch to the notifyDataSetChanged() then the items in list are removed and updated correctly, but then the cards dont animate.

Does someone has any experience in using the notifyItemRemoved() and know why it behaves differently than notifyDataSetChanged?

Here is some peiece of code that i am using:

private List<DetectedIssue> issues = new ArrayList<DetectedIssue>();

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    if(position >0){
        RiskViewHolder riskHolder = (RiskViewHolder)holder;
        final int index = position - 1;
        final DetectedIssue anIssue = issues.get(index);

        riskHolder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    int index = issues.indexOf(anIssue);
                    issues.remove(anIssue);
                    notifyItemRemoved(index);

                    //notifyDataSetChanged();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

@Override
public int getItemCount() {
    return (issues.size()+1);
}
revolutionary
  • 3,314
  • 4
  • 36
  • 53

8 Answers8

116

Use notifyItemRangeChanged(position, getItemCount()); after notifyItemRemoved(position);
You don't need to use index, just use position. See code below.

private List<DetectedIssue> issues = new ArrayList<DetectedIssue>();

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    if(position >0){
        RiskViewHolder riskHolder = (RiskViewHolder)holder;

        riskHolder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    issues.remove(position);
                    notifyItemRemoved(position);
                    //this line below gives you the animation and also updates the
                    //list items after the deleted item
                    notifyItemRangeChanged(position, getItemCount());

                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

@Override
public int getItemCount() {
    return issues.size();
}
Akshay Mahajan
  • 2,064
  • 1
  • 17
  • 20
  • 6
    From documentation: "You should only use the position parameter while acquiring the related data item inside this method and should not keep a copy of it. If you need the position of an item later on (e.g. in a click listener), use RecyclerView.ViewHolder.getAdapterPosition() which will have the updated adapter position." – Juan Cruz Soler Dec 13 '16 at 19:25
  • 1
    @Akshay Mahajan Old thread sorry, but `notifyItemRemoved(position);` works just fine alone (with animation). What `notifyItemRangeChanged(position, getItemCount());` is doing ? I can't see the difference. Thank you – Yohan Dahmani May 03 '17 at 15:36
  • Shouldn't be getItemCount() - position, as itemCount means the count of items after removed item? – Michał Ziobro Aug 05 '17 at 16:32
  • 1
    yes, the second parameter is range of changed items. using item count means that every item after the position is changing. – Travis Castillo Oct 04 '17 at 23:53
  • 3
    @YohanDahmani, notifyItemRemoved(position); won't work if you are trying on last item..IndexOutOfBoundException you gonna get. – Bajrang Hudda Aug 20 '18 at 13:49
  • 1
    This answer is nonsensical. Both `notifyItemRemoved` and `notifyItemRangeChanged` are doing the same thing. Why this answer has so many upvotes is a mystery. – Manuel Jul 04 '19 at 15:16
39

Tried

public void removeItem(int position) {
    this.taskLists.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position, getItemCount() - position);
}

and working like a charm.

Grender
  • 1,589
  • 2
  • 17
  • 44
  • 8
    Could you explain why you use `getItemCount() - position` instead of just `getItemCount()`? – hamena314 Apr 16 '18 at 20:59
  • @hamena314 actually notifyItemRangeChanged(int position, int itemCount) ment tha at "position" items have changed and from "position" , " itemCount" items have changed so it is wise that to pass only items after "positon" instead passing list of all the items. Or instead passing "getItemCount() - position" we can pass getItemCount(). – Jay Sep 25 '18 at 13:16
19

my mistake , notifyItemChanged(position) is helpless,the item of position can be removed ,and the item of position+1 is fine,but the items start from position+2,you will get an Exception, please use notifyItemRangeChanged(position,getItemCount()); after notifyItemRemoved(position);

like this:

public void removeData(int position) {
    yourdatalist.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position,getItemCount());
}
TikT
  • 191
  • 1
  • 3
2

Use this it is working perfectly.

        issues.remove(position);
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, issues.size());
1

As @pskink suggested it was supposed to be (index+1) in my case with notifyItemRemoved(index+1), probably because i am reserving the top index i.e. position=0 for a header.

Aditya Vyas-Lakhan
  • 13,409
  • 16
  • 61
  • 96
revolutionary
  • 3,314
  • 4
  • 36
  • 53
0

You can use getAdapterPosition() from the RecyclerView.ViewHolder

getLayoutPosition() provides the exact position of item in the layout and code is

holder.removeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Position for remove
                int modPosition= holder.getAdapterPosition();
                //remove item from dataset
                numbers.remove(modPosition);
                //remove item from recycler view
                if(numbers.isEmpty())
                  notifyDataSetChanged () 
                else
                  notifyItemRemoved(modPosition);
                
            }
        });
sree_sg
  • 719
  • 9
  • 11
  • 1
    You should use getAdapterPosition as the [documentation](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html#getLayoutPosition()) states, you´re on risk of creating inconsistencies. – marcos E. Feb 02 '17 at 16:03
  • getting error while removing last element of array list using this notifyItemRemoved(last pos) any solution for same? – Arbaz.in Feb 08 '21 at 09:51
  • can you check this? if(numbers.isEmpty()) notifyDataSetChanged () else notifyItemRemoved(modPosition); – sree_sg Feb 09 '21 at 10:17
0
**my solution looks like this**

this way is unnecessary to use the heavy method:
 //notifyItemRangeChanged(xx,xx)

/**
 * 
 * recyclerView的item中的某一个view,获取其最外层的viewParent,也就是item对应的layout在adapter中的position
 *
 * @param recyclerView
 * @param view:can be the deep one inside the item,or the item itself .
 * @return
 */
public static int getParentAdapterPosition(RecyclerView recyclerView, View view, int parentId) {
    if (view.getId() == parentId)
        return recyclerView.getChildAdapterPosition(view);
    View viewGroup = (View) view.getParent();
    if (viewGroup != null && viewGroup.getId() == parentId) {
        return recyclerView.getChildAdapterPosition(viewGroup);
    }
    //recursion
    return getParentAdapterPosition(recyclerView, viewGroup, parentId);
}




//wherever you set the clickListener .
holder.setOnClickListener(R.id.rLayout_device_item, deviceItemClickListener);
holder.setOnLongClickListener(R.id.rLayout_device_item, deviceItemLongClickListener);


@Override
public boolean onLongClick(View v) {
    final int position = ViewUtils.getParentAdapterPosition(rVDevicesList, v, R.id.rLayout_device_item);
    return true;
}
dong sheng
  • 266
  • 3
  • 10
0

In my case I use Content Provider and a Custom RecyclerView Adapter with Cursor. This line of code is where you notify:

getContext().getContentResolver().notifyChange(uri, null);

Assuming In your recyclerView adapter (Delete Button):

Uri currentUri = ContentUris.withAppendedId(DatabaseContract.ToDoEntry.CONTENT_URI_TODO, id);
int rowsDeleted = mContext.getContentResolver().delete(currentUri, null, null);
if (rowsDeleted == 0) {
    Log.d(TAG, "onClick: Delete failed");
} else {
    Log.d(TAG, "onClick: Delete Successful");
}

And in your Database Provider:

case TODO_ID:
selection = DatabaseContract.ToDoEntry._ID + "=?";
selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
rowsDeleted = database.delete(DatabaseContract.ToDoEntry.TODO_TABLE_NAME, selection, selectionArgs);
if (rowsDeleted != 0){
    getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
Daniel Puiu
  • 962
  • 6
  • 21
  • 29
MohammadL
  • 2,398
  • 1
  • 19
  • 36