I have problem with deleting item's from ArrayList and synchronising Adapter.
I have my RecyclerView adapter with some ArrayList inside it called items. I download some list from the server and dispaly inside it. Whenever I click on some of list items I would like to delete it from server, from local ArrayList and notify the adapter about it. The problem is that when I delete everything from down to up from the list everything is ok, but when f.e. I delete 1st element from the list and then randomly some of the elements it deletes element after the one I clicked. In some cases the app crashes (f.e. I delete 1st element then the last one). The error I get is f.e.:
java.lang.IndexOutOfBoundsException: Invalid index 4, size is 4
Look like it's something with list size but i don't know what is wrong?
Here is the function where I got position from (setPopUpListener(popupMenu, position)):
// Binding New View
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
RecipeItem item = items.get(position);
// Binding Recipe Image
Picasso.with(context).load(item.getImgThumbnailLink()).into(holder.recipeItemImage);
// Binding Recipe Title
holder.recipeItemTitle.setText(item.getTitle());
// Binding Recipe Subtitle
String subtitle = "Kuchnia " + item.getKitchenType() + ", " + item.getMealType();
holder.recipeItemSubtitle.setText(subtitle);
// Binding Recipe Likes Count
holder.recipeItemLikesCount.setText(Integer.toString(item.getLikeCount()));
// Binding Recipe Add Date
holder.recipeItemAddDate.setText(item.getAddDate());
// Binding Recipe Options Icon
holder.recipeItemOptionsIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu popupMenu = new PopupMenu(context, v);
setPopUpListener(popupMenu, position); // Setting Popup Listener
inflatePopupMenu(popupMenu); // Inflating Correct Menu
popupMenu.show();
}
});
// Item Click Listener
holder.setClickListener(new RecipeItemClickListener() {
@Override
public void onClick(View view, int position) {
// taking to recipe activity
}
});
}
Here is setPopUpListener() - just look at removeFromFavourites(position):
// Setting Popup Listener
private void setPopUpListener(PopupMenu popupMenu, final int position) {
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (popupType) {
// Add To Favourites Menu
case 0: {
switch (item.getItemId()) {
case R.id.item_add: {
addToFavourites(position);
return true;
}
}
}
// Remove From Favourites Menu
case 1: {
switch (item.getItemId()) {
case R.id.item_remove: {
removeFromFavourites(position);
return true;
}
}
}
}
return false;
}
});
}
Here is where the error appears (removeFromFavourites(position)):
// Removing User's Favourite
private void removeFromFavourites(int position) {
// Checking Connection Status
if (!FormValidation.isOnline(context)) {
showSnackbarInfo(context.getString(R.string.err_msg_connection_problem),
R.color.snackbar_error_msg);
} else {
SQLiteHandler db = new SQLiteHandler(context);
// Getting User Unique ID
String userUniqueId = db.getUserUniqueId();
db.close();
RecipeItem listItem = items.get(position);
// Getting Recipe Unique ID
String recipeUniqueId = listItem.getUniqueId();
// Removing From User's Favourites
removeFromUserFavouritesOnServer(recipeUniqueId, userUniqueId);
// Removing Item From Local Array List
items.remove(position);
// Notifying Adapter That Item Has Been Removed
notifyItemRemoved(position);
}
}
SOLUTION - HOPE THIS WILL HELP SOMEBODY
I have found the soution for this. If somebody will ever try to dynamicly remove elements from his array list and notifyItemRemoved(position) do not send clicked position as a parameter inside onBindViewHolder(ViewHolder holder, int position). You will meet with exactly the same situation as I did.
If you have 4 displayed elements in a list f.e. [0, 1, 2, 3] and try to remove from the end of the list everything will be fine cause clicked positions will match exactly the same positions in ArrayList. For example if you click 4th element:
position = 3 - position you will get when clicked on list element; myArray.remove(position) - will remove element with index = 3 and notifyItemRemoved(position) - will animate the list and remove deleted element from the displayed list. You are going to have following list: [0, 1, 2]. This is fine.
Situation changes when you want to delete random element. Let's say I want to delete 3rd displayed list element. I click on it to delete so i get:
position = 2 -> myArray.remove(position) -> notifyItemRemoved(position)
In this case the ArrayList I am going to get will be like this: [0, 1, 3]. In I now click on the last dispalyed element and would like to delete it that's what I will get:
position = 3->myArray.remove(position) -> notifyItemRemoved(position)
But what happens? App suddenly crashes with exception: java.lang.IndexOutOfBoundsException: Invalid index 3, size is 3. It means that we are trying to get element at the position that does not exist. But why? I got my clicked position from element... This is what happened:
At the beggining we had:
ARRAY LIST INDEXES -> [0, 1, 2, 3]
POSITIONS FROM CLICK -> [0, 1, 2, 3]
After Deleting 3rd element:
ARRAY LIST INDEXES -> [0, 1, 2]
POSITIONS FROM CLICK -> [0, 1, 3]
Now when I try to delete element at position = 3 we can't do that. We do not have that position. The max position we can get is 2. Thats why we get the exception. How to manage that problem?
In onBindViewHolder(ViewHolder holder, int position) we used position in
removeFromFavourites(position). But we also have our returned holder. If we use method called: getAdapterPosition() from class RecyclerView.ViewHolder we are at home.
getAdapterPosition
From developer site: http://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html#getAdapterPosition()
This will always return index identical to that in ArrayList. So summaring all we had to do was changing position parameter with holder.getAdapterPosition():
// Binding New View
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
RecipeItem item = items.get(position);
// Binding Recipe Image
Picasso.with(context).load(item.getImgThumbnailLink()).into(holder.recipeItemImage);
// Binding Recipe Title
holder.recipeItemTitle.setText(item.getTitle());
// Binding Recipe Subtitle
String subtitle = "Kuchnia " + item.getKitchenType() + ", " + item.getMealType();
holder.recipeItemSubtitle.setText(subtitle);
// Binding Recipe Likes Count
holder.recipeItemLikesCount.setText(Integer.toString(item.getLikeCount()));
// Binding Recipe Add Date
holder.recipeItemAddDate.setText(item.getAddDate());
// Binding Recipe Options Icon
holder.recipeItemOptionsIcon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu popupMenu = new PopupMenu(context, v);
setPopUpListener(popupMenu, holder.getAdapterPosition()); // Setting Popup Listener
inflatePopupMenu(popupMenu); // Inflating Correct Menu
popupMenu.show();
}
});
// Item Click Listener
holder.setClickListener(new RecipeItemClickListener() {
@Override
public void onClick(View view, int position) {
// taking to recipe activity
}
});
}
Use
notifyItemRangeChanged(position, getItemCount());
after
notifyItemRemoved(position);
You don't need to use index, just use position.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With