2

I'm developing an Android 3.1 application.

I have my custom ArrayAdapter. I want to show a list of names in a ListView.

These names are forms that could be download and save it locally. When user download and save one or more, I call updateFormsNotDownloaded(). But when I do that I get an IndexOutOfBoundsException. And I think this problem is because I call notifyDataSetChanged().

Look at my code:

public class FormAdapter extends ArrayAdapter<Form>
{
    private Context context;
    private int layoutResourceId;
    private List<Form> forms;
    private ArrayList<Integer> checkedItemsPosition;
    private Button downloadButton;

    public ArrayList<Integer> getCheckedItemsPosition()
    {
        return checkedItemsPosition;
    }

    public String[] getSelectedFormsId()
    {
        String[] ids = new String[checkedItemsPosition.size()];
        int i = 0;
        for(Integer pos : checkedItemsPosition)
        {
            Form f = forms.get(pos.intValue());
            ids[i] = f.FormId;
            i++;
        }
        return ids;
    }

    /**
     * Called when selected forms has been downloaded and save it locally correctly.
     */
    public void updateFormsNotDownloaded()
    {
        ArrayList<Form> copyForms = new ArrayList<Form>();
        for (int i = 0; i < forms.size(); i++)
        {
            if (!checkedItemsPosition.contains(new Integer(i)))
                copyForms.add(forms.get(i));
        }
        forms = copyForms;
        checkedItemsPosition.clear();
        notifyDataSetChanged();
    }

    public FormAdapter(Context context, int textViewResourceId,
            List<Form> objects, Button downloadButton)
    {
        super(context, textViewResourceId, objects);

        this.context = context;
        this.layoutResourceId = textViewResourceId;
        this.forms = objects;
        this.checkedItemsPosition = new ArrayList<Integer>();
        this.downloadButton = downloadButton;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent)
    {
        View row = convertView;
        if (row == null)
        {
            LayoutInflater inflater = ((Activity)context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);
        }

        Form f = forms.get(position);
        if (f != null)
        {
            CheckBox checkBox = (CheckBox)row.findViewById(R.id.itemCheckBox);
            if (checkBox != null)
            {
                checkBox.setText(f.Name);
                checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener()
                {
                    public void onCheckedChanged(CompoundButton buttonView,
                            boolean isChecked)
                    {
                        //Form f = forms.get(position);
                        if (isChecked)
                        {
                            //checkedItems.add(f.FormId);
                            checkedItemsPosition.add(new Integer(position));
                        }
                        else
                        {
                            //checkedItems.remove(checkedItems.indexOf(f.FormId));
                            checkedItemsPosition.remove(checkedItemsPosition.indexOf(new Integer(position)));
                        }
                        downloadButton.setEnabled(checkedItemsPosition.size() > 0);
                    }
                });
            }
        }

        return row;
    }
}

I had three items on forms, but I remove one of them.

Why am I getting that exception?

This is exception log:

java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at es.viacognita.adapters.FormAdapter.getView(FormAdapter.java:89)
at android.widget.AbsListView.obtainView(AbsListView.java:1949)
at android.widget.ListView.makeAndAddView(ListView.java:1756)
at android.widget.ListView.fillDown(ListView.java:656)
at android.widget.ListView.fillSpecific(ListView.java:1314)
at android.widget.ListView.layoutChildren(ListView.java:1587)
at android.widget.AbsListView.onLayout(AbsListView.java:1800)
at android.view.View.layout(View.java:9581)
at android.view.ViewGroup.layout(ViewGroup.java:3877)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1542)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1403)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1314)
at android.view.View.layout(View.java:9581)
at android.view.ViewGroup.layout(ViewGroup.java:3877)
at android.widget.FrameLayout.onLayout(FrameLayout.java:400)
at android.view.View.layout(View.java:9581)
at android.view.ViewGroup.layout(ViewGroup.java:3877)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1542)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1403)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1314)
at android.view.View.layout(View.java:9581)
at android.view.ViewGroup.layout(ViewGroup.java:3877)
at android.widget.FrameLayout.onLayout(FrameLayout.java:400)
at android.view.View.layout(View.java:9581)
at android.view.ViewGroup.layout(ViewGroup.java:3877)
at android.view.ViewRoot.performTraversals(ViewRoot.java:1253)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2003)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)
VansFannel
  • 45,055
  • 107
  • 359
  • 626
  • where is **forms** is initialised..? – ngesh Apr 18 '12 at 09:44
  • forms is initialised on an AsyncTask. – VansFannel Apr 18 '12 at 09:45
  • 1
    I dont see it here but i am assuming u have overriden getCount()? – Shubhayu Apr 18 '12 at 09:57
  • @Shubhayu No, I haven't do that. I'm reading this question http://stackoverflow.com/questions/3669325/notifydatasetchanged-example. And I think it is a better approach than mine. – VansFannel Apr 18 '12 at 10:01
  • you can change its behavior when u extend from it and change it to what suits u best. But in this case, i think overriding getCount() should fix ure problem. u can however always optimize ure code to make it more robust :) – Shubhayu Apr 18 '12 at 10:05
  • @VansFannel I don't think you need the check suggested by WarrenFaith. – Shubhayu Apr 18 '12 at 10:30
  • @Shubhayu I have overrided getCount and it seems to work. But it doesn't. Items are check boxes. I see that first item has been removed, but second one now it is checked. If I try to unchecked it, I get an IndexOutOfBoundsException. I'm getting a headache. – VansFannel Apr 18 '12 at 10:35
  • I totally sympathize with you. I have had miserable times figuring out checkboxes in items and deleting them. I think the problem that is happening now is that your second arraylist containing the checkeditems info is not getting properly updated. Take a break and start over again. Go through the complete program flow. If you think there is any other code snippet that could be added, add it in ure post. I'll try and see if i can catch the problem. – Shubhayu Apr 18 '12 at 10:40
  • @Shubhayu I have to re-set ArrayAdapter to ListView after removing elements to avoid get first element checked. – VansFannel Apr 18 '12 at 12:08

5 Answers5

7

Override getCount(). And return the value forms.size() in it.

Shubhayu
  • 13,402
  • 5
  • 33
  • 30
5

In my case the answer of Shubhayu was not enough. getCount and getItem must be synchronous and use the same list object. If your array adapter must use an internal List of items both need to be overriden.

public class MyArrayAdapter extends ArrayAdapter<MyItemType> {

private final List<MyItemType> items;

public MyArrayAdapter(final Context _context, final int _resource, final List<MyItemType> _items) {
  super(_context, _resource, _items);

  this.items = _items;
}

// IMPORTANT: either override both getCount and getItem or none as they have to access the same list
@Override
public int getCount() {
  return this.items.size();
};

@Override
public Site getItem(final int position) {
  return this.items.get(position);
}

...
L. G.
  • 9,642
  • 7
  • 56
  • 78
2

Position is one based while array index is zero based.

So change that line:

if (!checkedItemsPosition.contains(new Integer(i)))
    copyForms.add(forms.get(i - 1));
WarrenFaith
  • 57,492
  • 25
  • 134
  • 150
  • When I select first ListView item, checkedItemsPosition contains 0. – VansFannel Apr 18 '12 at 10:22
  • @WarrenFaith I can't seem to figure out how this applies here. the "i" in this case is from 0 - forms.size()-1. I don't think we need this 1/0 check here. Am I missing something? – Shubhayu Apr 18 '12 at 10:23
1

You're using two Lists and their indexes - but these lists are in no way synced (they can change individually without checking the other).

Why not instead use a ArrayList<Form> checkForms and ArrayList<Form> uncheckedForms, then you could remove the reference to the form from uncheckedForms and add it to checkedForms which would keep both List's in sync.

When you needed to get all Forms you could then simply return a union of both ArrayLists.

Graeme
  • 25,714
  • 24
  • 124
  • 186
0

No one has answered why he is getting the exception yet. So I will provide my answer here even though his question is quite old.

The reason you're getting the exception is because when you're updating "forms", you create a new array object (thus a new reference) and change the reference of forms to it while ArrayAdapter maintains its own array object mObjects, to which the constructor copies the reference of the array object you provided (objects). You can see this if you look into its source code (good thing it is open source).

To truly solve the problem you should be updating the array using the inherited functions add(...), addAll(...), etc. Or just extend baseadapter and make your own implementations.

Pui Ho Lam
  • 232
  • 1
  • 2
  • 11