45

I've a couple of question I haven't been able to figure out.

I'm trying to get all the checked elements from a ListView but:

  1. If I check and then uncheck an element, it's returned as "checked" by the getCheckedItemPositions() function

  2. I don't know how can I iterate through this:

    SparseBooleanArray checked = list.getCheckedItemPositions();
    
Kev
  • 118,037
  • 53
  • 300
  • 385
sergi
  • 969
  • 5
  • 14
  • 22
  • give a look, maybe helps : http://stackoverflow.com/questions/4019308/android-list-getcheckeditempositions-always-returns-null http://stackoverflow.com/questions/3996938/why-is-listview-getcheckeditempositions-not-returning-correct-values http://stackoverflow.com/questions/4010623/android-click-on-listitem-checks-wrong-checkbox – rBaggio Jan 28 '11 at 19:21
  • 1
    starting API level 11 you can use `list.getCheckedItemCount();` – Rajkiran Nov 27 '13 at 15:28

10 Answers10

81

The other answers using SparseBooleanArray are nearly correct, but they are missing one important thing: SparseBooleanArray.size() will sometimes only return the count of true values. A correct implementation that iterates over all the items of the list is:

SparseBooleanArray checked = list.getCheckedItemPositions();

for (int i = 0; i < list.getAdapter().getCount(); i++) {
    if (checked.get(i)) {
        // Do something
    }
}
Jarett Millard
  • 5,802
  • 4
  • 41
  • 48
  • 7
    Ironically, this is the only correct (and sane) answer and didn't have a single vote. – mseddon Dec 21 '13 at 01:55
  • 1
    Why do you need to iterate over the whole list? Isn't the getCheckedItemPositions() method designed to avoid this? Why just not iterate over **already selected for you by getCheckedItemPositions() method** items? `Foo objectAtCheckedRow = null; for (int i = 0; i < positions.size(); i++) { /*positions.size() == 2*/ objectAtCheckedRow = adapter.getItem(positions.keyAt(i)); /*Do something significant with object here*/ }` – Sergey Feb 18 '15 at 09:38
  • @Sergey It's possible to do it that way if you only need the checked items, but you can't assume that all the items returned by `getCheckedItemPositions()` are checked. Try checking an item and then unchecking it; the `SparseBooleanArray` will now contain an entry for that position with a value of `false`. – Jarett Millard Feb 20 '15 at 14:54
  • @Jarett First, the author asked how to get all **checked** items. (And it is reasonable). Next, why would you check an item then get `SparseBooleanArray` object then uncheck the item (and possibly rotate the device) and then use the stale `SparseBooleanArray` object? Do all check and uncheck actions first and then get fresh `SparseBooleanArray` object and use it. Anyway if you apply the code in your answer to the described scenario it will yield same result as my code except that my code will iterate over only a few checked items whereas your code will iterate over the whole list. – Sergey Feb 21 '15 at 07:30
  • The code I showed in my comment I got from Kevin Worth's answer to this question: http://stackoverflow.com/questions/4590856/how-to-get-selected-items-from-multi-select-list-view – Sergey Feb 21 '15 at 07:45
  • @Sergey I'm not talking about using a stale SparseBooleanArray; I'm saying that if the user checks an item and then unchecks it before you call `getCheckedItemPositions()` it can still appear in the retrieved array with a value of `false`. If the boolean states were always true then the call could simply return an array of ints for checked positions. – Jarett Millard Feb 23 '15 at 19:52
  • The is an old question, but I just stumbled upon it and it seems to me that there is a bug in the answer with the most votes. The SpareBooleanArray get(int key) method takes a key, but here it is being passed an index. If the keys are [10, 99, 100] and the values are [true, true, true], you will be asking for [0, 1, 2] and receiving [false, false, false] The correct answer is: for (int i = 0; i < list.getAdapter().getCount(); i++) { if (checked.ValueAt(i)) { // Do something } } – MikeWallaceDev Feb 25 '15 at 01:44
  • @Jarett I am sorry, I didn't fully understand your previous comment: "... you can't assume that all the items returned by getCheckedItemPositions() are checked. Try checking an item and then unchecking it; the SparseBooleanArray will now contain an entry for that position with a value of false". – Sergey Feb 25 '15 at 12:35
  • 1
    @Jarett So yes, my code is wrong and it wouldn't yield same result as yours. I must check each item returned from SparseBooleanArray: `for (int i = 0; i < positions.size(); i++) { if (positions.valueAt(i)) { Foo objectAtCheckedRow = adapter.getItem(positions.keyAt(i)); /*Do something significant with object here*/ } }` But still I think there is no need to iterate over whole list. – Sergey Feb 25 '15 at 12:36
  • @MikeWallaceDev The code will function properly as written since it iterates over every possible position. No key will be more than `list.getAdapter().getCount()` so all possible keys will be checked. To Sergey's point, if you don't iterate over every item then you would need to use `valueAt(i)`. – Jarett Millard Feb 25 '15 at 17:56
  • @Sergey I agree, iterating over the list is not always necessary. However, sometimes you will want to process both checked and unchecked items. This method is also guaranteed to preserve the order of the checked items, which can be important for some use cases. Also, the number of items in a checkable list is limited by practicality: nobody is going to scroll through a list of thousands of items and check some off, so the performance gain is very small. I think the upsides outweigh the negligible performance decrease of iterating through the entire list. – Jarett Millard Feb 25 '15 at 18:06
  • @JarettMillard , you are correct. I went to look at the ListView code and it does indeed the full list of values, not just the checked ones. – MikeWallaceDev Feb 25 '15 at 18:14
  • 2
    Actually I ran into a problem and **only** this answer is correct. I have a list with up to 10 check-boxes. If the last two are checked, the 'getCheckedItemPositions()' will return the entire list, but if I ask for the size it will return 2, since only 2 items are checked, and only iterate through the first two items, never reaching the checked ones. Only by getting the size from the ListAdapter as demonstrated by Jarett can I iterate through the entire list. This should be marked as the correct answer. – ckn Dec 24 '15 at 20:00
11

I solved my case with this:

public class MyAdapter extends BaseAdapter{
    public HashMap<String,String> checked = new HashMap<String,String>();
....
    public void setCheckedItem(int item) {


        if (checked.containsKey(String.valueOf(item))){
            checked.remove(String.valueOf(item));
        }

        else {
            checked.put(String.valueOf(item), String.valueOf(item));
        }
    }
        public HashMap<String, String> getCheckedItems(){
        return checked;
    }
}

To set an element is checked:

public class FileBrowser extends Activity implements OnClickListener{        
private ListView list;

    ...
    list.setOnItemClickListener(new OnItemClickListener(){

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int item,
                long id) {

                        BrowserAdapter bla = (BrowserAdapter) parent.getAdapter();
                        bla.setCheckedItem(item);
                    }
    });

Then to get the checked items from outside the class..

MyAdapter bAdapter;    
Iterator<String> it = bAdapter.getCheckedItems().values().iterator();
                    for (int i=0;i<bAdapter.getCheckedItems().size();i++){
                        //Do whatever
                        bAdapter.getItem(Integer.parseInt(it.next());
                    }

Hope it can help someone.

sergi
  • 969
  • 5
  • 14
  • 22
  • This did exactly what I needed it to. If i wanted to limit selection to 1 item, how should I protect against that? – AdamMc331 Jan 03 '15 at 23:26
  • I think it will not work unless you do it.next() - 1 when iterating in the for loop through the entire collection. By doing it.next(), in case checkeditems list contains only 1 item it will throw NoSuchElementException and in case it has more than 1 item, first item is skipped. – Willy Jan 19 '15 at 21:36
8

Jarett's answer is great, but this should be a bit faster, since it's only checking the elements in the sparse array that are present in the underlying array (only those can be true):

SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
    final int checkedItemCount = checkedItemPositions.size();
    for (int i = 0; i < checkedItemCount; i++) {
        int key = checkedItemPositions.keyAt(i);
        if (checkedItemPositions.get(key)) {
            doSomething(key);
        } else {
            // item was in the sparse array, but not checked.
        }
    }

Pro tip: look at the source of SparseBooleanArray, it's a pretty simple class:

http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.0.3_r1/android/util/SparseBooleanArray.java/?v=source

treesAreEverywhere
  • 3,722
  • 4
  • 28
  • 51
6

You can iterate through the SparseBooleanArray using a for loop and the SparseBooleanArray's size() and get(index) methods.

EDIT 3/2/2014: People have pointed out that SparseBooleanArray's size() method returns the number of checked values, rather than the true length of the array, so I have mended my answer to account for that. Essentially it is the same, except that rather than iterating for the length of the array, we iterate until we have found all checked items. Since we only care about the number of checked items, it's irrelevant that with this method, we may not actually get to the end of the array (we won't see anything past the last checked item).

SparseBooleanArray checked = list.getCheckedItemPositions();

int numChecked = checked.size();

for (int i = 0; numChecked > 0; i++){
    if (checked.get(i)){
        //the item at index i is checked, do something
        numChecked--; //We found a checked item, so decrement the number of checked items remaining
    }
    else
        //the item is not checked, do something else
}
Chris
  • 343
  • 3
  • 9
  • 1
    SparseBooleanArray.size() will only return the count of true values not of all values. – Richard Feb 24 '14 at 13:39
  • SparseBooleanArray.size() ... "Returns the number of key-value mappings that this SparseBooleanArray currently stores." from http://developer.android.com/reference/android/util/SparseBooleanArray.html#size() . that is not necessarily only true values. – treesAreEverywhere Mar 25 '14 at 01:10
  • 3
    Yes, but the size of the `SparseBooleanArray` returned from `ListView.getCheckedItemPositions()` will only be equal to the number of checked items. Unchecked items will not show up in the `SparseBooleanArray`. – Jarett Millard Mar 25 '14 at 15:11
6

My brain didn't like looping through the SparseBooleanArray and I didn't need a custom adapter, so the following was a little more intuitive for me:

  1. Don't forget to use CHOICE_MODE_MULTIPLE in onCreate():

    getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

  2. Use the following method to get an ArrayList of them:

    // This example is to get an ArrayList of names (Strings)
    protected ArrayList<String> getNames() {
        ArrayList<String> names = new ArrayList<String>();
    
        for (int i = 0; i < getListView().getCount(); i++) {
            if (getListView().isItemChecked(i)) {
                // Do whatever you need to in here to get data from
                // the item at index i in the ListView
                names.add(mUsers.get(i).getName());
            }
        }
    
        return names;
    } 
    
Ben Jakuben
  • 3,147
  • 11
  • 62
  • 104
  • 2
    This is perfect. No reason why anyone should plunge into the hell hole that is SparseBooleanArray. – David Nov 15 '14 at 15:41
2
 final long[] checkedIds = lv.getCheckItemIds();
            for (int i = 0; i < checkedIds.length; i++) {
                Log.d("checkedIds", "id checked: " + checkedIds[i]);
            }
hector6872
  • 1,336
  • 14
  • 18
  • 2
    NB: This method is deprecated and you should use `getCheckedItemIds()` instead (available from API 8 / Android 2.2). – Timmmm Aug 20 '12 at 16:02
1

I had the same problem and here is my solution with SparseBooleanArray :

SparseBooleanArray checkedPositions = lv.getCheckedItemPositions ();
int size = checkedPositions.size ();
for (int i=0 ; i<size ; i++) {
     // We get the key stored at the index 'i'
     int key = checkedPositions.keyAt (i);
     // We get the boolean value with the key
     Log.i (Tag, "checkedPositions(" + key + ")=" + checkedPositions.get (key));
}
Idriss
  • 11
  • 1
1

Just saw the question and I was facing the same problem.

There is a simpler solution using SparseBooleanArray to exactly count how many items are checked. This is my onClick procedure:

@Override
public void onClick(View v) {
switch(v.getId()) {
  case R.id.button:
    SparseBooleanArray a = listView.getCheckedItemPositions();
    if(checked(vArray)>0) {
            String vCheckedList = "";
            for (int i = 0; i < nLength; i++) {
                if (a.valueAt(i) && i < nLength-1 && a.size()>1)
                    vCheckedList += listView.getAdapter().getItem(vArray.keyAt(i))+"\n";
                else if (a.valueAt(i))
                    vCheckedList += listView.getAdapter().getItem(vArray.keyAt(i));
            }
            Toast.makeText(getApplicationContext(), vCheckedList+ " is checked", Toast.LENGTH_SHORT).show(); 
            a.clear();
        } else
            Toast.makeText(getApplicationContext(), "No Item is Selected", Toast.LENGTH_SHORT).show();
        break;
    default:
        break;
        }
}

The checked method:

private int checked(SparseBooleanArray vArray) {
    int vCounter = 0;
    for(int i=0;i<vArray.size(); i++)
        if(vArray.valueAt(i))
            vCounter++;
    return vCounter;

}

It will solve both problem of the checked items.

Christoper Hans
  • 625
  • 4
  • 19
0

In it's entirety this is the solution I used.

Where pos is the Recyclerview item position

SparseBooleanArray selected = new SparseBooleanArray();

//OnClick listener here or whatever you use to check or uncheck an item
if (selected.get(pos, false)) {
        selected.delete(pos);
    } else {
        selected.put(pos, true);
    }

selected.size() will reveal how many items are selected

selected.get(pos) will return true if it's selected

The following as has been stated, iterates over all those items which were selected:

for (int i = 0; i < list.getAdapter().getCount(); i++) {
    if (selected.get(pos)) {
        // Do stuff
    }
}
Mullazman
  • 507
  • 5
  • 15
0

Here is how I get checked items from recyclerview adapter

fun getAllCheckedItems(outList){
         for (position in spareBooleanArray.keyIterator()) {
            outList.add(mList.get(position)
        }
}
Nasib
  • 1,173
  • 1
  • 13
  • 23