0

Android and Java noob here, although I've dabbled in various languages over the years. This has been driving me bonkers all week.

Trying to write my first app, and it's the clichéd shopping list app, with a ListView made up of CheckedTextView items (as supplied by android.R.layout.simple_list_item_multiple_choice). The ListView is set to CHOICE_MODE_MULTIPLE.

The backend to the ListView is an ArrayList, called shoppingItems, where ShoppingListItem is simply defined as:

public class ShoppingListItem {

    public String name;
    public Boolean checked;

    // The obvious constructors here...

}

and I have an ArrayAdapter with an over-ridden getView() method:

shoppingListAdapter = new ArrayAdapter<ShoppingListItem>
                        (this,
                        android.R.layout.simple_list_item_multiple_choice,
                        android.R.id.text1,
                        shoppingItems)
{
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        CheckedTextView rowView = (CheckedTextView)convertView;

        if (rowView==null){
            LayoutInflater inflater = getLayoutInflater();
            rowView = (CheckedTextView)inflater.inflate(android.R.layout.simple_list_item_multiple_choice, parent, false);
        }               
        rowView.setText(shoppingItems.get(position).name);
        rowView.setChecked(shoppingItems.get(position).checked);
        return rowView;
    }
};

Everything works fine -- adding items, editing items, removing an individual item via its context menu -- except removing all the checked items via a "Remove" button at the bottom of the screen.

I must have tried writing my removeCheckedItems method half a dozen different ways, including various combinations of:

  • removing checked items using the list adapter (which from all I've read is the way that's supposed to work)
  • removing checked items directly from the ArrayList, then calling notifyDatasetChanged()
  • explicitly removing child views from the ListView
  • iterating over the results of the ListView's getCheckedItemPositions(), rather than the whole list

Here's my most naive attempt:

private void removeCheckedItems(){

    ShoppingListItem item;
    for (int i=0; i< adapter.getCount(); i++) {
        item = shoppingListAdapter.getItem(i);
        if (shoppingListView.isItemChecked(i)){
            item = shoppingItems.get(i);
            shoppingListAdapter.remove(item);
        }
    }
    removeBtn.setEnabled(false);
}

However I do it, though: the checkboxes in the ListView just don't stay in sync with the data in the ShoppingItems ArrayList. Specifically, if I start off with:

Item one   [ ]
Item two   [ ]
Item three [ ]

in the list, then check Item one:

Item one   [x]
Item two   [ ]
Item three [ ]

then click my Remove button, which confirms the action via a popup dialog, the first item disappears, but the checkbox in the first row remains checked:

Item two   [x]
Item three [ ]

At this point, I know by means of debugging messages etc. that the contents of the ArrayList are correct -- i.e. that it contains two ShoppingListItem items with the correct names, both of whose 'checked' fields are set to false.

I'm sure I'm missing something obvious, but despite reading numerous examples, and even more ListView-related answers on here, I can't see it for the life of me. (Complete code listing for the activity as it currently stands can be found here, if you need to see more.)

calum_b
  • 233
  • 4
  • 12

2 Answers2

3

You just need to call the fillData() method again from your removeCheckedItems method.

Everytime you make a change to the data, you need to "fill" the data for the list again, refresh the list adapter.

Do a Google Search for 'ListActivity fillData' tutorial, and you get lots of great examples.

Here are a couple good ones:

Let me know if you get stuck, I'll be able to help more tomorrow. I've done this a lot, so I can help you work the kinks out of it.

Bryan
  • 3,629
  • 2
  • 28
  • 27
  • Thanks, have just had a quick look at those links and will see if I can adapt it to my code. What I guess I don't understand is why adapter.notifyDatasetChanged() isn't doing this work for me, when I call adapter.remove(item)? It's supposed to "Notify the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself"... – calum_b Nov 16 '11 at 15:45
  • Where are you calling removeCheckedItems() from? Is it called from your main application class? And are you using threads at all? I didn't implement my listview using arrayAdapters. Instead, I used a SimpleCursorAdapter. I'm not sure of the pros and cons of using arrayAdapters vs. SimpleCursorAdapters. – Bryan Nov 16 '11 at 16:21
  • At the moment, I'm calling it from the onClickListener for the remove button, which is in the main application class. The button is in the same LinearLayout as the ListView. No threads. – calum_b Nov 16 '11 at 16:40
  • Update: making another call to shoppingListView.setAdapter(the-same-adapter-as-before) after I've removed the items seems to almost work, which seems to be much the same as the suggested call to fillData() was supposed to do. (I say 'almost' because it's still occasionally throwing an assertion... if that turns out to be a bug at my end, I'll accept this as the answer.) – calum_b Nov 16 '11 at 17:02
  • Spoke too soon... seemed to work in the case where I checked and removed one item at a time, but still gets out of sync when I try to check and remove two or more items at a time. – calum_b Nov 17 '11 at 00:44
  • Ok, I found a logic bug on my end too, so with that fixed and the extra call to setAdapter in my removeCheckedItems method (which I really still don't understand the requirement for), it seems to work now. – calum_b Nov 17 '11 at 01:38
  • The extra call to setadapter basically says redisplay the list data on the screen because the data in the list has changed. – Bryan Nov 17 '11 at 01:49
  • But the [ArrayAdapter API docs](http://developer.android.com/reference/android/widget/ArrayAdapter.html#setNotifyOnChange%28boolean%29) say that should happen automatically when I call the adapter's add, remove, insert or clear methods, which is what I'm doing. Or that it should happen when I explicitly call adapter.notifyDataSetChanged(), if I'd initially called adapter.setNotifyOnChanged(false), which I've tried as well. – calum_b Nov 17 '11 at 12:35
  • It seems a lot of people have had similar issues as yours. I went with the SimpleCursorAdapter method, using fillData, and have had very few issues with it. Here is a link that deals with issues with notifyDataSetChanged(): http://stackoverflow.com/questions/3669325/notifydatasetchanged-example/5092426#5092426 – Bryan Nov 17 '11 at 14:53
0

For the record, the only way I ever got this to work in the end was to use my own custom row layout and inflate that in getView(), rather than using android.R.layout.simple_list_item_multiple_choice. Everything worked just the way I'd expected it to all along when I did that, including the ListView updating instantly and correctly whenever I changed the data via the ArrayAdapter, and notifyDatasetChanged() doing likewise when I was changing the data directly.

calum_b
  • 233
  • 4
  • 12