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
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:
ListView
updates its list of selected itemsnotifyDataSetChanged()
on your adapter, and it notifies its observers that the dataset has changed. The ListView
is one of those observers.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.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.