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)
Override getCount(). And return the value forms.size() in it.
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);
}
...
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));
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 ArrayList
s.
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.
来源:https://stackoverflow.com/questions/10206783/removing-elements-on-arraylist-throw-an-indexoutofboundsexception-on-arrayadapte