Skip to content Skip to sidebar Skip to footer

Android - Custom Listview Adapter - Multi Selection Remove - Indexoutofbounds - Why?

I have a custom Listview using a adapter class to extend ArrayAdapter of a Item class. I have the ability to change between choice modes of NONE,Single and Multi. This all works

Solution 1:

There appears to be a bug in AbsListView that causes this issue. It can happen with any subclass of AbsListView, including ListView and GridView.

In single- and multi-choice mode, the ListView responds to a notifyDataSetChanged() call on its adapter by verifying the set of checked items in confirmCheckedPositionsById(). Since the selected item(s) have already been deleted from the dataset at that point, the adapter will throw an exception. Notably, this issue only occurs if the adapter's hasStableIds() method returns true.

Loosely speaking, this is the relevant path:

  1. You select one or more items in the ListView
  2. The ListView updates its list of selected items
  3. You click the delete button
  4. The item(s) are removed from your dataset
  5. You call notifyDataSetChanged() on your adapter, and it notifies its observers that the dataset has changed. The ListView is one of those observers.
  6. Next time the ListView is redrawn, it sees the adapter's notification and calls handleDataChanged(). At this point, the ListView still thinks that our now-deleted items are selected and in the dataset.
  7. The handleDataChanged() method calls confirmCheckedPositionsById(), which in turn tries to call getItemId() on the adapter using a stale position. If the deleted item happens to be near the end of the list, this position is likely to be out of bounds for the array, and the adapter will throw IndexOutOfBoundsException.

Two possible workarounds are as follows:

  • Create a new adapter every time the dataset changes, as noted in other answers. This has the unfortunate effect of losing the current scroll position unless it's saved and restored manually.

  • Clear the selected items by calling clearChoices() on the ListView (or GridView) before you call notifyDataSetChanged() on the adapter. The selected items will be deleted anyhow, so losing the current selection state is unlikely to be a problem. This method will preserve the scroll position and should prevent flickering while the list is being updated.

Solution 2:

The bug in confirmCheckedPositionsById (bullet #7 in acj's answer) causes getItemId to get called on a stale position. However, it will get called again with the correct position to refresh the layout. When I ran into this problem I updated the custom Adapter's getItemId like so

@Override
public long getItemId(int position) {
    return position < getCount() ? getItem(position).getId() : -1;
}

Solution 3:

Ok I solved the issue!!! YAY!

What I had to do to prevent the IndexOutOFBounds Exception was to reset the list view adapter so to refresh the list view contents. So the magic line was

 listView.setAdapter(mMyListViewAdapter);

However I believe this is not the best practise to use when working with a list view and its better to update the content of the adapter that the list view is attached to. But I not quite sure how to go about that?

Anyway he is my updated remove method code.

privatevoidremoveItems() {
    finalSparseBooleanArraycheckedItems= listView.getCheckedItemPositions();

    if (checkedItems != null) {
        finalintcheckedItemsCount= checkedItems.size();

        // Lets get the position of the view to scroll to before the first checked // item to restore scroll position//          inttopPosition= checkedItems.keyAt(0) - 1;            

        listView.setAdapter(null);
        for (inti= checkedItemsCount - 1; i >= 0 ; i--) {
            // This tells us the item position we are looking at// --finalintposition= checkedItems.keyAt(i);

            // This tells us the item status at the above position// --finalbooleanisChecked= checkedItems.valueAt(i);
            if (isChecked) {
                Itemitem= mMyListViewAdapter.getItem(position);
                mMyListViewAdapter.remove(item);
                //mMyListViewAdapter.notifyDataSetChanged();

            }               
        }
        listView.setAdapter(mMyListViewAdapter);
        //if topPosition is -1 then item zero is positioned by default.
        listView.setSelection(topPosition);
    }
}

Solution 4:

This is a documented bug, please vote for it:

https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=64596

My use case is the ListView configured as ListView.CHOICE_MODE_SINGLE, I tried @Peter Tran's suggestion without any luck. Here is the workaround that is successful for me:

    myAdapter.deleteRow(listView.getCheckedItemPosition());
    int checkedIndex = listView.getCheckedItemPosition();
    System.out.println("checkedIndex="+checkedIndex);

    int count=myAdapter.getCount();
    if (checkedIndex==count) listView.setItemChecked(count-1, true);

My test is to manually select the last item on the list(count-1). I omitted the handling for count==0 case but that will most likely be needed. I observed that println() always prints the index of the deleted row. myAdapter.deleteRow() notifies of data change to listeners which says to me ListView isn't properly updating it's checked indices. I've tried this code with hasStableIds() returning both true and false from the custom adapter with the same results.

Solution 5:

Filter using AlertDialog.Builder fully customized :-

((TextView) TabBarWithCustomStack.vPublic.findViewById(R.id.tv_edit)) .setVisibility(View.GONE);
    classDialogSelectionClickHandlerimplementsDialogInterface.OnMultiChoiceClickListener {
        publicvoidonClick(DialogInterface dialog, int clicked,
                boolean selected) {
            Log.i("ME", hosts.toArray()[clicked] + " selected: " + selected);
        }
    }

    classDialogButtonClickHandlerimplementsDialogInterface.OnClickListener {
        publicvoidonClick(DialogInterface dialog, int clicked) {
            switch (clicked) {
            case DialogInterface.BUTTON_POSITIVE:
                filteredList.clear();
                statusesSelected.clear();

                for (inti=0; i < statusesStringArray.length; i++) {
                    // Log.i( "ME", statuses.toArray()[ i ] + " selected: "// + isSelectedStatuses[i] );if (isSelectedStatuses[i] == true) {

                        if (statuses.get(i).toString()
                                .equalsIgnoreCase("Accepted")) {
                            statusesSelected.add("1");
                        } elseif (statuses.get(i).toString()
                                .equalsIgnoreCase("Rejected")) {
                            statusesSelected.add("2");
                        } elseif (statuses.get(i).toString()
                                .equalsIgnoreCase("Pending")) {
                            statusesSelected.add("3");
                        }

                    }
                    isSelectedStatuses[i] = false;
                }

                CalendarcurrentCalender= Calendar.getInstance(Locale
                        .getDefault());

                DatecurrentDate=newDate(
                        currentCalender.getTimeInMillis());

                if (listSelected == 0) {
                    for (intj=0; j < arr_BLID.size(); j++) {
                        if (Helper.stringToDate(
                                arr_BLID.get(j).getStart_ts().toString(),
                                Helper.SERVER_FORMAT).after(currentDate)) {
                            if (statusesSelected.contains(arr_BLID.get(j)
                                    .getStatus())) {
                                filteredList.add(arr_BLID.get(j));
                            }
                        }
                    }

                } else {
                    for (intj=0; j < arr_BLID.size(); j++) {
                        if (currentDate.after(Helper.stringToDate(arr_BLID
                                .get(j).getStart_ts().toString(),
                                Helper.SERVER_FORMAT))) {
                            if (statusesSelected.contains(arr_BLID.get(j)
                                    .getStatus())) {
                                filteredList.add(arr_BLID.get(j));
                            }
                        }
                    }

                }

                lvBeeps.setAdapter(newEventsAdapter(ctx));

                break;

            case DialogInterface.BUTTON_NEGATIVE: {

                currentCalender = Calendar.getInstance(Locale.getDefault());

                currentDate = newDate(currentCalender.getTimeInMillis());

                if (listSelected == 0) {

                    filteredList.clear();
                    for (inti=0; i < arr_BLID.size(); i++) {
                        if (Helper.stringToDate(
                                arr_BLID.get(i).getStart_ts().toString(),
                                Helper.SERVER_FORMAT).after(currentDate)) {
                            filteredList.add(arr_BLID.get(i));
                        }

                        if (i < isSelectedStatuses.length) {
                            isSelectedStatuses[i] = false;
                        } else {
                            continue;
                        }
                    }

                    lvBeeps.setAdapter(newEventsAdapter(ctx));
                } else {

                    filteredList.clear();
                    for (inti=0; i < arr_BLID.size(); i++) {
                        if (currentDate.after(Helper.stringToDate(arr_BLID
                                .get(i).getStart_ts().toString(),
                                Helper.SERVER_FORMAT))) {
                            filteredList.add(arr_BLID.get(i));
                        }

                        if (i < isSelectedStatuses.length) {
                            isSelectedStatuses[i] = false;
                        } else {
                            continue;
                        }

                    }

                    lvBeeps.setAdapter(newEventsAdapter(ctx));

                }
            }
                break;
            }
        }
    }

    btnHost = (Button) view.findViewById(R.id.btnHost);
    btnHost.setOnClickListener(newOnClickListener() {

        @OverridepublicvoidonClick(View v) {
            // TODO Auto-generated method stub// ((TextView)((RelativeLayout)getActivity().getLayoutInflater().inflate(R.layout.event_filter_title,// null)).findViewById(R.id.tvDialogTitle)).setText("Hosts");// Log.d( "Dialog object",// " static made dialog in checkbox--> "+((TextView)((RelativeLayout)getActivity().getLayoutInflater().inflate(R.layout.event_filter_title,// null)).findViewById(R.id.tvDialogTitle)).getText());finalLayoutInflaterinflater123= (LayoutInflater) getActivity()
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            finalViewview123= inflater123.inflate(
                    R.layout.event_filter_title, null);

            // Log.d( "Dialog object",// " static made dialog in view123--> "+view123);//// Log.d( "Dialog object",// " static made dialog in checkbox--> "+((CheckBox)view123.findViewById(R.id.cbSelectAll)));//// Log.d( "Dialog object",// " static made dialog in checkbox--> "+((CheckBox)inflater.inflate(R.layout.event_filter_title,// (ViewGroup) ((AlertDialog)// BeepsFragment.dialog).getCurrentFocus(),// true).findViewById(R.id.cbSelectAll)));//// Log.d( "Dialog object",// " static made dialog in checkbox--> "+((CheckBox)(((RelativeLayout)getActivity().getLayoutInflater().inflate(R.layout.event_filter_title,// null)).findViewById(R.id.cbSelectAll))));
            hostsDialog = newAlertDialog.Builder(getActivity())
                    .setCustomTitle(/*
                                     * Html.fromHtml(
                                     * "<b><font color=\"purple\">  Host</font></b>"
                                     * )
                                     */view123)
                    .setIcon(
                            getActivity().getResources().getDrawable(
                                    R.drawable.add_host))
                    .setMultiChoiceItems(hostsStringArray, isSelectedHosts,
                            newOnMultiChoiceClickListener() {

                                // android.content.DialogInterface.OnShowListener// ocl = new// DialogInterface.OnShowListener() {//// @Override// public void onShow(DialogInterface// dialog) {// // TODO Auto-generated method stub// BeepsFragment.dialog = dialog;// }// };publicvoidonClick(DialogInterface dialog,
                                        int clicked, boolean selected) {

                                    booleanall=true;
                                    Log.i("ME", hosts.toArray()[clicked]
                                            + " selected: " + selected);

                                    // for (int i = 0; i <// isSelectedHosts.length; i++ ){//// if(isSelectedHosts[i]==true){// all = true;// }else{// all = false;// }// }//// Log.i( "ME", all + " selected:--- "// +((CheckBox)view123.findViewById(R.id.cbSelectAll)));//// if(all = true){// ((CheckBox)view123.findViewById(R.id.cbSelectAll)).setChecked(true);// }else{// ((CheckBox)view123.findViewById(R.id.cbSelectAll)).setChecked(false);// }

                                    Log.d("Dialog object",
                                            " static made dialog --> "
                                                    + BeepsFragment.dialog
                                                    + " from parameter dialog --> "
                                                    + dialog);

                                }

                            })
                    .setPositiveButton(
                            Html.fromHtml("<b><font color=\"purple\">Apply Filter</font></b>"),
                            newDialogButtonClickHandler() {

                                // android.content.DialogInterface.OnShowListener// ocl = new// DialogInterface.OnShowListener() {//// @Override// public void onShow(DialogInterface// dialog) {// // TODO Auto-generated method stub// BeepsFragment.dialog = dialog;// }// };publicvoidonClick(DialogInterface dialog,
                                        int clicked) {
                                    switch (clicked) {
                                    case DialogInterface.BUTTON_POSITIVE:

                                        filteredList.clear();
                                        hostsSelected.clear();

                                        for (inti=0; i < hostsStringArray.length; i++) {
                                            Log.i("ME",
                                                    hosts.toArray()[i]
                                                            + " selected: "
                                                            + isSelectedHosts[i]
                                                            + "\n\n\t hostStringArray-->"
                                                            + hostsStringArray[i]);

                                            if (isSelectedHosts[i] == true) {

                                                hostsSelected.add(hosts
                                                        .get(i));
                                                isSelectedHosts[i] = false;
                                            }
                                            // isSelectedHosts[i] = false;
                                        }

                                        CalendarcurrentCalender= Calendar
                                                .getInstance(Locale
                                                        .getDefault());

                                        DatecurrentDate=newDate(
                                                currentCalender
                                                        .getTimeInMillis());

                                        if (listSelected == 0) {
                                            for (intj=0; j < arr_BLID
                                                    .size(); j++) {
                                                if (Helper
                                                        .stringToDate(
                                                                arr_BLID.get(
                                                                        j)
                                                                        .getStart_ts()
                                                                        .toString(),
                                                                Helper.SERVER_FORMAT)
                                                        .after(currentDate)) {
                                                    if (hostsSelected
                                                            .contains(arr_BLID
                                                                    .get(j)
                                                                    .getHost_name())) {
                                                        filteredList
                                                                .add(arr_BLID
                                                                        .get(j));
                                                    }
                                                    if (hostsSelected
                                                            .contains("Me"))
                                                        if (BeepApplication
                                                                .getSelfId() == arr_BLID
                                                                .get(j)
                                                                .getHost_id())
                                                            filteredList
                                                                    .add(arr_BLID
                                                                            .get(j));
                                                }
                                            }

                                        } else {
                                            for (intj=0; j < arr_BLID
                                                    .size(); j++) {
                                                if (currentDate.after(Helper
                                                        .stringToDate(
                                                                arr_BLID.get(
                                                                        j)
                                                                        .getStart_ts()
                                                                        .toString(),
                                                                Helper.SERVER_FORMAT))) {
                                                    if (hostsSelected
                                                            .contains(arr_BLID
                                                                    .get(j)
                                                                    .getHost_name())) {
                                                        filteredList
                                                                .add(arr_BLID
                                                                        .get(j));
                                                    }
                                                    if (hostsSelected
                                                            .contains("Me"))
                                                        if (BeepApplication
                                                                .getSelfId() == arr_BLID
                                                                .get(j)
                                                                .getHost_id())
                                                            filteredList
                                                                    .add(arr_BLID
                                                                            .get(j));
                                                }
                                            }

                                        }
                                        lvBeeps.setAdapter(newEventsAdapter(
                                                ctx));
                                        break;
                                    }
                                }

                            })
                    .setNegativeButton(
                            Html.fromHtml("<b><font color=\"purple\">Remove Filter</font></b>"),
                            newDialogButtonClickHandler() {

                                publicvoidonClick(
                                        final DialogInterface dialog,
                                        int clicked) {

                                    CalendarcurrentCalender= Calendar
                                            .getInstance(Locale
                                                    .getDefault());

                                    DatecurrentDate=newDate(
                                            currentCalender
                                                    .getTimeInMillis());

                                    if (listSelected == 0) {

                                        filteredList.clear();
                                        for (inti=0; i < arr_BLID.size(); i++) {
                                            if (Helper.stringToDate(
                                                    arr_BLID.get(i)
                                                            .getStart_ts()
                                                            .toString(),
                                                    Helper.SERVER_FORMAT)
                                                    .after(currentDate)) {
                                                filteredList.add(arr_BLID
                                                        .get(i));
                                                if (i < isSelectedHosts.length) {
                                                    isSelectedHosts[i] = false;
                                                } else {
                                                    continue;
                                                }
                                            }
                                        }

                                        lvBeeps.setAdapter(newEventsAdapter(
                                                ctx));
                                    } else {

                                        filteredList.clear();
                                        for (inti=0; i < arr_BLID.size(); i++) {
                                            if (currentDate.after(Helper
                                                    .stringToDate(
                                                            arr_BLID.get(i)
                                                                    .getStart_ts()
                                                                    .toString(),
                                                            Helper.SERVER_FORMAT))) {
                                                filteredList.add(arr_BLID
                                                        .get(i));
                                                if (i < isSelectedHosts.length) {
                                                    isSelectedHosts[i] = false;
                                                } else {
                                                    continue;
                                                }
                                            }
                                        }

                                        lvBeeps.setAdapter(newEventsAdapter(
                                                ctx));

                                    }
                                }
                            });

            finalAlertDialogdlg= hostsDialog.create();

            dlg.show();

            ((TextView) view123.findViewById(R.id.tvDialogTitle))
                    .setText("Hosts");

            ((CheckBox) view123.findViewById(R.id.cbSelectAll))
                    .setOnCheckedChangeListener(newOnCheckedChangeListener() {

                        @OverridepublicvoidonCheckedChanged(
                                CompoundButton buttonView, boolean isChecked) {
                            // TODO Auto-generated method stubif (isChecked == true) {

                                ListViewlist= dlg.getListView();
                                for (inti=0; i < list.getCount(); i++) {

                                    isSelectedHosts[i] = true;
                                    list.setItemChecked(i, true);

                                }

                            }

                            elseif (isChecked == false) {

                                ListViewlist= dlg.getListView();
                                for (intj=0; j < list.getCount(); j++) {

                                    isSelectedHosts[j] = false;
                                    list.setItemChecked(j, false);
                                }
                                // }
                            }

                        }
                    });

        }
    });

    btnStatus = (Button) view.findViewById(R.id.btnStatus);
    btnStatus.setOnClickListener(newOnClickListener() {

        @OverridepublicvoidonClick(View v) {
            // TODO Auto-generated method stubLayoutInflaterinflater123= (LayoutInflater) getActivity()
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            Viewview123= inflater123.inflate(R.layout.event_filter_title,
                    null);

            statusDialog = newAlertDialog.Builder(getActivity())
                    .setIcon(
                            getActivity().getResources().getDrawable(
                                    R.drawable.add_host))
                    .setCustomTitle(/*
                                     * Html.fromHtml(
                                     * "<b><font color=\"purple\">  Status</font></b>"
                                     * )
                                     */view123)
                    .setIcon(
                            getActivity().getResources().getDrawable(
                                    R.drawable.add_host))
                    .setMultiChoiceItems(statusesStringArray,
                            isSelectedStatuses,
                            newDialogSelectionClickHandler())
                    .setPositiveButton(
                            Html.fromHtml("<b><font color=\"purple\">Apply Filter</font></b>"),
                            newDialogButtonClickHandler() {

                            })
                    .setNegativeButton(
                            Html.fromHtml("<b><font color=\"purple\">Remove Filter</font></b>"),
                            newDialogButtonClickHandler());

            finalAlertDialogdlg= statusDialog.create();

            dlg.show();

            ((TextView) view123.findViewById(R.id.tvDialogTitle))
                    .setText("Status");
            ((CheckBox) view123.findViewById(R.id.cbSelectAll))
                    .setOnCheckedChangeListener(newOnCheckedChangeListener() {

                        @OverridepublicvoidonCheckedChanged(
                                CompoundButton buttonView, boolean isChecked) {
                            // TODO Auto-generated method stubif (isChecked == true) {

                                ListViewlist= dlg.getListView();
                                for (inti=0; i < list.getCount(); i++) {

                                    isSelectedStatuses[i] = true;
                                    list.setItemChecked(i, true);

                                }

                            }

                            elseif (isChecked == false) {

                                ListViewlist= dlg.getListView();
                                for (intj=0; j < list.getCount(); j++) {

                                    isSelectedStatuses[j] = false;
                                    list.setItemChecked(j, false);
                                }
                                // }
                            }

                        }
                    });

        }
    });

Post a Comment for "Android - Custom Listview Adapter - Multi Selection Remove - Indexoutofbounds - Why?"