title: Android - Custom ListView Adapter - Multi Selection remove - Indexoutofbounds - why?
body: <p>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 fine.  What I am trying to implement now is a method to removes items from the list view (and adapter) with multiple selection when in the multi choice mode.  However I get IndexOutOFBounds Exceptions when doing either of the following;
  1) remove last item in listview in SINGLE choice mode (Note: anything before last item remove ok)
  2) In multi selection choice mode I once again can not remove last item
  3) again in multi selection mode I can remove single selected items before last item but 2 or more selections result in index out of bounds errors again.</p>
  
  <p>I added debug log to show position being removed and size of getCheckItemPositions() and my for loop counter (e.g. i) and finally the item title of the item being removed.  If I comment out the actual listadpter.remove(position) line then the log output seems to indicator all is working fine  So I am now suspecting the issue falls into my adapter class getView method.  But my brain is exhausted and I am stuck.  </p>
  
  <p>MainActivity.class - removeItems method called from a button view object;</p>
  
  <pre><code>private void removeItems() {
      final SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
      //final long[] checkedItemIds = listView.getCheckedItemIds();
      final int checkedItemsCount = checkedItems.size();
  
      Log.d("drp", "Adapter Count is: " + Integer.toString(mMyListViewAdapter.getCount()));
      if (checkedItems != null) {
          for (int i = checkedItemsCount-1; i &gt;= 0 ; --i) {
              // This tells us the item position we are looking at
              // --
              final int position = checkedItems.keyAt(i);
              // This tells us the item status at the above position
              // --
              final boolean isChecked = checkedItems.valueAt(i);
  
              if (isChecked) {
                  Item item = mMyListViewAdapter.getItem(position);
                  Log.d("drp", "removing : " + Integer.toString(position) + " of " +Integer.toString(checkedItemsCount) + "-" + Integer.toString(i) + " - Title: " + mMyListViewAdapter.getItem(position).getTitle());
                  mMyListViewAdapter.remove(item);
  
              }
          }
      }
  }
  </code></pre>
  
  <p>Adapter Class;</p>
  
  <pre><code>public class MyListViewAdapter extends ArrayAdapter&lt;Item&gt;  implements OnItemClickListener{
  
  private LayoutInflater mInflator;
  
  /**
   * This is my view holder for getView method so don't need to call
   * findViewById all the time which results in speed increase
   */
  static class ViewHolder {
  
      public TextView txtTitle;
      public TextView txtDescription;
      public TextView txtSessionCount;
      public ImageView listThumbnailImage;
      public ImageView listStatusIndicatorImage;
      public InertCheckBox Checkbox;
  }
  
  /**
   * Constructor from a list of items
   */
  public MyListViewAdapter(Context context, List&lt;Item&gt; items) {
      super(context, 0, items);
      mInflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }
  
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
      // This is how you would determine if this particular item is checked
      // when the view gets created
      // --
      final ListView lv = (ListView) parent;
      final boolean isChecked = lv.isItemChecked(position);
      final int selectionMode = lv.getChoiceMode();
  
      // The item we want to get the view for
      // --
      Item item = getItem(position);
  
      // Re-use the view if possible (recycle)
      // --
      ViewHolder holder = null;
      if (convertView == null) {
          convertView = mInflator.inflate(R.layout.listview_row, null);
          holder = new ViewHolder();
          holder.txtTitle = (TextView) convertView.findViewById(R.id.title);
          holder.txtDescription = (TextView) convertView.findViewById(R.id.description);
          holder.txtSessionCount = (TextView) convertView.findViewById(R.id.session_count);
          holder.listThumbnailImage = (ImageView) convertView.findViewById(R.id.list_image);
          holder.listStatusIndicatorImage = (ImageView) convertView.findViewById(R.id.status);
          holder.Checkbox = (InertCheckBox) convertView.findViewById(R.id.inertCheckBox);
          convertView.setTag(holder);
      } else {
          holder = (ViewHolder)convertView.getTag();
      }
      holder.txtTitle.setText(item.getTitle());
      holder.txtDescription.setText(item.getDescription());
      holder.txtSessionCount.setText(item.getSessionCount());
      holder.listThumbnailImage.setImageBitmap((Bitmap) item.getThumbnailImage());        
      switch (selectionMode) {
      case ListView.CHOICE_MODE_NONE:
          holder.Checkbox.setVisibility(InertCheckBox.GONE);
          holder.listStatusIndicatorImage.setVisibility(ImageView.VISIBLE);
          holder.listStatusIndicatorImage.setImageBitmap((Bitmap) item.getListIndicatorImage());
          break;
      //case ListView.CHOICE_MODE_SINGLE: case ListView.CHOICE_MODE_MULTIPLE:
      default:
          holder.listStatusIndicatorImage.setVisibility(ImageView.GONE);
          holder.Checkbox.setVisibility(InertCheckBox.VISIBLE);
          holder.Checkbox.setButtonDrawable(R.drawable.checkbox);
          holder.Checkbox.setChecked(isChecked);
          break;
      }           
  
  
      return convertView;
  }
  
  @Override
  public long getItemId(int position) {
      return getItem(position).getId();
  }
  
  @Override
  public boolean hasStableIds() {
      return true;
  }
  </code></pre>
  
  <p>And Item Class - first half;</p>
  
  <pre><code>public class Item implements Comparable&lt;Item&gt; {
  
  private long id;
  private String title;
  private String description;
  private String session_count;
  private Bitmap listImage;
  private Bitmap statusImage;
  
  public Item(long id, String title, String description, String session_count, Bitmap listImage, Bitmap statusImage) {
      super();
      this.id = id;
      this.title = title;
      this.description = description;
      this.session_count = session_count;
      this.listImage = listImage;
      this.statusImage = statusImage;
  }
  
  public long getId() {
      return id;
  }
  
  public void setId(long id) {
      this.id = id;
  }
  
  public String getTitle() {
      return title;
  }
  </code></pre>
  
  <p>Here is visual of my debug log tracking item removals</p>
  
  <pre><code>07-23 22:59:14.910: D/drp(19104): Adapter Count is: 51
  07-23 22:59:14.910: D/drp(19104): removing : 50 of 4-3 - Title: Test 50 - testing
  07-23 22:59:14.910: D/drp(19104): removing : 49 of 4-2 - Title: Test 49 - testing
  07-23 22:59:14.910: D/drp(19104): removing : 48 of 4-1 - Title: Test 48 - testing
  </code></pre>
  
  <p>Again if I comment out the "mMyListViewAdapter.remove(item);" line in MainActivity not crashes and log seems to indicated its working as expected.  Can anyone see my error that results in my Index Out Of Bounds Exception?</p>
  
  <p>Also I am using SDK 4.0.4 API 15.</p>
  
  <p>Many Thanks,</p>
  
  <p>Paul.</p>
  
  <p>Addition - full log output</p>
  
  <pre><code>        07-25 00:21:53.235: D/AbsListView(25952): Get MotionRecognitionManager
          07-25 00:21:53.270: D/dalvikvm(25952): GC_CONCURRENT freed 89K, 3% free 13027K/13383K, paused 1ms+2ms
          07-25 00:21:53.430: D/dalvikvm(25952): GC_CONCURRENT freed 207K, 4% free 13232K/13703K, paused 3ms+2ms
          07-25 00:21:53.630: D/CLIPBOARD(25952): Hide Clipboard dialog at Starting input: finished by someone else... !
          07-25 00:21:54.930: D/dalvikvm(25952): GC_FOR_ALLOC freed 189K, 4% free 13331K/13767K, paused 10ms
          07-25 00:21:54.930: I/dalvikvm-heap(25952): Grow heap (frag case) to 13.610MB for 408976-byte allocation
          07-25 00:21:54.940: D/dalvikvm(25952): GC_FOR_ALLOC freed 6K, 4% free 13724K/14215K, paused 9ms
          07-25 00:21:54.950: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 4% free 13724K/14215K, paused 9ms
          07-25 00:21:54.950: I/dalvikvm-heap(25952): Grow heap (frag case) to 13.994MB for 408976-byte allocation
          07-25 00:21:54.960: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 4% free 14124K/14663K, paused 9ms
          07-25 00:21:54.970: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 4% free 14124K/14663K, paused 9ms
          07-25 00:21:54.975: I/dalvikvm-heap(25952): Grow heap (frag case) to 14.384MB for 408976-byte allocation
          07-25 00:21:54.995: D/dalvikvm(25952): GC_CONCURRENT freed 0K, 4% free 14523K/15111K, paused 1ms+1ms
          07-25 00:21:55.005: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 4% free 14523K/15111K, paused 9ms
          07-25 00:21:55.005: I/dalvikvm-heap(25952): Grow heap (frag case) to 14.774MB for 408976-byte allocation
          07-25 00:21:55.020: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 5% free 14923K/15559K, paused 9ms
          07-25 00:21:55.030: D/dalvikvm(25952): GC_FOR_ALLOC freed &lt;1K, 5% free 14923K/15559K, paused 9ms
          07-25 00:21:55.030: I/dalvikvm-heap(25952): Grow heap (frag case) to 15.165MB for 408976-byte allocation
          07-25 00:21:55.040: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 5% free 15322K/16007K, paused 10ms
          07-25 00:21:55.055: D/dalvikvm(25952): GC_FOR_ALLOC freed 0K, 5% free 15722K/16455K, paused 9ms
          07-25 00:21:55.110: D/dalvikvm(25952): GC_FOR_ALLOC freed 157K, 5% free 16145K/16903K, paused 9ms
          07-25 00:21:56.565: E/SKIA(25952): FimgApiStretch:stretch failed
          07-25 00:21:56.690: E/SKIA(25952): FimgApiStretch:stretch failed
          07-25 00:21:56.710: E/SKIA(25952): FimgApiStretch:stretch failed
          07-25 00:22:00.545: D/drp(25952): Adapter Count is: 51
          07-25 00:22:00.545: D/drp(25952): removing : 49 of 2-2 - Title: Test 49 - testing
          07-25 00:22:00.545: D/drp(25952): removing : 48 of 2-1 - Title: Test 48 - testing
          07-25 00:22:00.545: D/drp(25952): removing : 47 of 2-0 - Title: Test 47 - testing
          07-25 00:22:00.550: D/AndroidRuntime(25952): Shutting down VM
          07-25 00:22:00.550: W/dalvikvm(25952): threadid=1: thread exiting with uncaught exception (group=0x40c6f1f8)
          07-25 00:22:00.560: E/AndroidRuntime(25952): FATAL EXCEPTION: main
          07-25 00:22:00.560: E/AndroidRuntime(25952): java.lang.IndexOutOfBoundsException: Invalid index 48, size is 48
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at java.util.ArrayList.get(ArrayList.java:304)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.ArrayAdapter.getItem(ArrayAdapter.java:337)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at au.drp.mylistview.MyListViewAdapter.getItemId(MyListViewAdapter.java:107)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.AbsListView.confirmCheckedPositionsById(AbsListView.java:5956)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.AbsListView.handleDataChanged(AbsListView.java:5999)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.ListView.layoutChildren(ListView.java:1535)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.AbsListView.onLayout(AbsListView.java:2254)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.View.layout(View.java:11467)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewGroup.layout(ViewGroup.java:4237)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.RelativeLayout.onLayout(RelativeLayout.java:925)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.View.layout(View.java:11467)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewGroup.layout(ViewGroup.java:4237)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.View.layout(View.java:11467)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewGroup.layout(ViewGroup.java:4237)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1644)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1502)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1415)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.View.layout(View.java:11467)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewGroup.layout(ViewGroup.java:4237)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.View.layout(View.java:11467)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewGroup.layout(ViewGroup.java:4237)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1721)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2678)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.os.Handler.dispatchMessage(Handler.java:99)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.os.Looper.loop(Looper.java:137)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at android.app.ActivityThread.main(ActivityThread.java:4514)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at java.lang.reflect.Method.invokeNative(Native Method)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at java.lang.reflect.Method.invoke(Method.java:511)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)
          07-25 00:22:00.560: E/AndroidRuntime(25952):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
  </code></pre>