Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Adapter "java.lang.IndexOutOfBoundsException: Invalid index 4, size is 4"

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);
    }
}
like image 773
anton86993 Avatar asked Dec 24 '15 20:12

anton86993


2 Answers

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
            }

        });
    }
like image 194
anton86993 Avatar answered Nov 15 '22 08:11

anton86993


Use

notifyItemRangeChanged(position, getItemCount()); 

after

notifyItemRemoved(position);

You don't need to use index, just use position.

like image 21
CodeCody Avatar answered Nov 15 '22 10:11

CodeCody