using notifyItemRemoved or notifyDataSetChanged with RecyclerView in Android
Asked Answered
A

8

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);
}
Analisaanalise answered 28/1, 2015 at 10:2 Comment(7)
try notifyItemRemoved(index+1)Matchlock
Probably because you're removing different indexesVaporize
The index is correct. As i said everything works fine if i use notifyDataSetChanged() instead.....Analisaanalise
did you try notifyItemRemoved(index+1)?Matchlock
Wow, my exact problem! Thanks for saving me the trouble of simplifying my code to make the question clear.Alternately
Having the same problem, index+1 didn't work and the Range method didn't work also.Bicorn
liist.remove(position); notifyItemRemoved(position); notifyItemRangeChanged(position, getItemCount()); For removing every time top most element.Eolande
A
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.

Analisaanalise answered 28/1, 2015 at 11:23 Comment(0)
C
117

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();
}
Congregationalism answered 18/12, 2015 at 7:8 Comment(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."Scorpaenid
@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 youLauber
Shouldn't be getItemCount() - position, as itemCount means the count of items after removed item?Sedulity
yes, the second parameter is range of changed items. using item count means that every item after the position is changing.Ereshkigal
@YohanDahmani, notifyItemRemoved(position); won't work if you are trying on last item..IndexOutOfBoundException you gonna get.Waldner
This answer is nonsensical. Both notifyItemRemoved and notifyItemRangeChanged are doing the same thing. Why this answer has so many upvotes is a mystery.Haldeman
Z
39

Tried

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

and working like a charm.

Zampino answered 16/1, 2016 at 14:46 Comment(2)
Could you explain why you use getItemCount() - position instead of just getItemCount()?Obligate
@Obligate 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().Lacerated
W
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());
}
Wieldy answered 11/1, 2016 at 9:43 Comment(2)
please provide details on what will change by doing this and how it will help resolve the issue.Hembree
this worked. the position of the row object will also be updated.Blade
E
2

Use this it is working perfectly.

        issues.remove(position);
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, issues.size());
Eatables answered 27/9, 2021 at 6:49 Comment(0)
A
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.

Analisaanalise answered 28/1, 2015 at 11:23 Comment(0)
A
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);
                
            }
        });
Alpaca answered 17/5, 2016 at 12:1 Comment(3)
You should use getAdapterPosition as the documentation states, you´re on risk of creating inconsistencies.Wilinski
getting error while removing last element of array list using this notifyItemRemoved(last pos) any solution for same?Kendrakendrah
can you check this? if(numbers.isEmpty()) notifyDataSetChanged () else notifyItemRemoved(modPosition);Alpaca
M
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;
}
Marxism answered 17/8, 2016 at 9:20 Comment(0)
C
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;
Churr answered 16/4, 2018 at 16:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.