Why is ListView.getCheckedItemPositions() not returning correct values?

Viewed 28321

The app has a ListView with multiple-selection enabled, in the UI it works as expected. But when I read the values out using this code:

Log.i(TAG,"Entered SearchActivity.saveCategoryChoice()");
SparseBooleanArray checkedPositions = categorySelector.getCheckedItemPositions();
Log.i(TAG,"checkedPositions: " + checkedPositions.size());

if (checkedPositions != null) {
  int count = categoriesAdapter.getCount();
  for ( int i=0;i<count;i++) {
    Log.i(TAG,"Selected items: " + checkedPositions.get(i));
  }
}

I get this output, no matter what state each checkbox is in:

Entered SearchActivity.saveCategoryChoice()
checkedPositions: 0
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false
Selected items: false

The SparseBooleanArray seems to return false for any non-existent item, so the source of the problems seems to be that getCheckedItemPositions() is returning an empty array. The method is behaving as if there are no items in the ListView, but there are.

I can see from the docs that no values are returned when the ListView is not set up as multi-select, but it is, using this statement:

categorySelector.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

In my scenario, the adapter I'm using is a subclass of ArrayAdapter, and (without any solid evidence) I suspect this may be the cause, though I can't see why it shouldn't work.

15 Answers

There is no need to handle the checking/unchecking of items within the ListView. It already does it on its own.

What does not seem documented is that the ListView will only do this if:

  1. a ListAdapter is set and
  2. the choice mode is CHOICE_MODE_MULTIPLE and
  3. the ids used by the ListAdapter are stable.

The third point was what drove me crazy for a while.

I am not sure what 'stable' means (I guess that the ids don't ever change while the list is displayed). As far as the ListView is concerned, it means that the method hasStableIds() in ListAdapter returns true.

I created a simple subclass of ArrayAdapter like this:

public class StableArrayAdapter<T> extends ArrayAdapter<T> {

    public StableArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
        super(context, textViewResourceId, objects);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }
}

(You already have your subclass, so just add the hasStableIds override)

Of course, one needs to add the constructor that one was using with ArrayAdapter.

Once you use this class as your ListAdapter, getCheckedItemPositions() behaves as expected.

One last note: setChoiceMode must be called AFTER setting the list adapter.

This adjustment fixed it for me. Change the getView method so that "int position" is "final int position". Then do this with your checkbox:

((CheckBox) convertView).setOnCheckedChangeListener(new OnCheckedChangeListener() {

                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    list.setItemChecked(position, ((CheckBox) buttonView).isChecked());
                }
            });

Let me elaborate. list is a reference to the listview and in my case the adapter is an inner class in the dialog holding the list.

I found this thread by having the same problem but I think I have come up with a workaround that worked for me for unkown reasons. Whenever I tried getting a value I got nothing but if I loop through the list setting all to false it started working just like intended.

This was actually a feature I had implemented where the user could either "Select All" or "Unselect All". I run this method in my onCreate.

private void selectNone() {
    ListView lv = getListView();
    for (int i = 0; i < lv.getCount(); i++) {
        lv.setItemChecked(i, false);
    }
}

Now all my values are correct. For getting the values, in my case, just Strings.

private void importSelected() {     
    ListView lv = getListView();
    SparseBooleanArray selectedItems = lv.getCheckedItemPositions();

    for (int i = 0; i < selectedItems.size(); i++) {
        if (selectedItems.get(i)) {
            String item =  lv.getAdapter().getItem(selectedItems.keyAt(i)).toString();
        }
    }

    selectNone(); //Reset
}

I hope this helps someone.

Related