K3NN3TH K3NN3TH - 1 month ago 6
Android Question

Android ArrayIndexOutOfBoundsException and AbsListViewRecycleBin.addScrapView

I have an

ArrayIndexOutOfBoundsException
that randomly comes up. It seems to be happening during my
notifyDataSetChanged();
Since the error is so random it makes it difficult to pin point exactly where it is happening.

Has anyone had similar issues with custom
Adaptor
?

FATAL EXCEPTION: main
java.lang.ArrayIndexOutOfBoundsException: length=12; index=12
at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:8041)
at android.widget.ListView.layoutChildren(ListView.java:1604)
at android.widget.AbsListView.onLayout(AbsListView.java:2444)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
at android.view.View.layout(View.java:15221)
at android.view.ViewGroup.layout(ViewGroup.java:4793)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2260)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2007)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1249)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6364)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:791)
at android.view.Choreographer.doCallbacks(Choreographer.java:591)
at android.view.Choreographer.doFrame(Choreographer.java:561)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:777)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5455)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
at dalvik.system.NativeStart.main(Native Method)




public class CarUpfitScanvinadapter extends BaseAdapter{
@SuppressWarnings("unused")
private final String TAG = this.getClass().getSimpleName();
private Activity mActivity;
private ArrayList<CarUpfitModel> mData;
private static LayoutInflater sInflater = null;
public int height = 0;
public int heightSet = 0;
private CarUpfitModelForm mForm;
private ScanlistListener mCallback;

public CarUpfitScanvinadapter(Activity a, ArrayList<CarUpfitModel> d, Resources resLocal){
mActivity = a;
mData = d;
sInflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public void removeItem(int i){
mData.remove(i);
this.notifyDataSetChanged();
}

@Override
public int getCount() {
Log.v(TAG, "getCount");
if(mData.size()<=0) return 1;
return mData.size();
}

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

@Override
public int getViewTypeCount() {
return getCount();
}

@Override
public CarUpfitModel getItem(int position) {
return mData.get(position);
}

@Override
public int getItemViewType(int position) {
return position;
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if(convertView == null){
vi = sInflater.inflate(R.layout.subaruupfitscanlistview, parent, false);
mForm = new CarUpfitModelForm();
mForm.setllScanlist((LinearLayout) vi.findViewById(R.id.llScanlist));
mForm.getllScanlist().setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,heightSet));
mForm.setllheightset((LinearLayout) vi.findViewById(R.id.llheightset));
mForm.setetModel((EditText) vi.findViewById(R.id.etModel));
mForm.settvScanlistvin((TextView) vi.findViewById(R.id.tvScanlistvin));
mForm.settvScanlistdate((TextView) vi.findViewById(R.id.tvScanlistdate));
mForm.setcbGbkit((CheckBox) vi.findViewById(R.id.cbGbkit));
mForm.setcbFltmat((CheckBox) vi.findViewById(R.id.cbFltmat));
mForm.setcbFlmatbrk((CheckBox) vi.findViewById(R.id.cbFlmatbrk));
mForm.setcbEyesight((CheckBox) vi.findViewById(R.id.cbEyesight));
mForm.setcbTrnkpan((CheckBox) vi.findViewById(R.id.cbTrnkpan));
mForm.setcbIntmirror((CheckBox) vi.findViewById(R.id.cbIntmirror));
mForm.setcbExtmirror((CheckBox) vi.findViewById(R.id.cbExtmirror));
mForm.setcbPzevbadge((CheckBox) vi.findViewById(R.id.cbPzevbadge));
mForm.setivDelete((ImageView) vi.findViewById(R.id.ivDelete));
mForm.setivSave((ImageView) vi.findViewById(R.id.ivSave));
mForm.getivSave().setTag(mForm);
vi.setTag(mForm);
}else{
mForm = (CarUpfitModelForm) vi.getTag();
}
mForm.getllScanlist().setOnClickListener(new OnItemClickListener(position, CarUpfit.ITEM_CLICK));
if(mData.size()<=0){
mForm.puttvScanlistvin("No Data");
}else{
mForm.puttvScanlistvin(getItem(position).getVin());
mForm.puttvScanlistdate(getItem(position).getDate());
mForm.setId(getItem(position).getid());
mForm.putjson(getItem(position).getData());
mForm.getivSave().setOnClickListener(new OnItemClickListener(position, CarUpfit.ITEM_UPDATE));
mForm.getivDelete().setOnClickListener(new OnItemClickListener(position, CarUpfit.ITEM_REMOVE));
}
return vi;
}

private class OnItemClickListener implements OnClickListener{
private int mPosition;
private int mType;

OnItemClickListener(int position, int type){
mPosition = position;
mType = type;
}
@Override
public void onClick(View v) {
mCallback.scanlistclick(v, mPosition, mType);
}
}

public void setimplements(CarUpfit sl) {
try {
mCallback = (ScanlistListener) sl;
} catch (ClassCastException e) {
throw new ClassCastException("CarUpfit must implement ScanlistListener");
}
}

public interface ScanlistListener{
public void scanlistclick(View v, int position, int type);
}
}


EDITS

I have a list of items with animation that expands the list items. this is why I have overrides on getViewTypeCount(). If I do not override it when a user opens an item it will open others also.

Screen shot of items closed:

items closed

Screen shot of item open:

enter image description here

Answer

You shouldn't override getViewTypeCount() and getItemViewType() if all your list items are uniform, or at least make them return 1 and 0 respectively.

By overriding them the way you did, first of all you inhibit view-recycling mechanism of ListView, and second this probably leads to crash you described, because when you remove an item from the middle of the list, the types of the views after removed one become changed.

UPDATE: Actually, this is what is happening: ListView calls getViewTypeCount() on the adapter only in setAdapter() and creates an internal array of that size. So if you later add views and your getItemViewType() returns an index greater than or equal to that size, you get ArrayIndexOutOfBoundsException.

UPDATE 2: If you need expandable list view, just use ExpandableListView.

UPDATE 3: OK, I'll explain as thoroughly as I can.

There's no reason to open views off the screen. ListView is designed to perform recycling and inhibiting it is unneeded in 99.9% of time, and your case doesn't fall into remaining 0.01%.

Your adapter should remember all the details and populate the newly appearing views when the ListView is being scrolled.

In your case you have two types of views - open and closed, so getViewTypeCount() should return 2 and getItemViewType() should return 0 or 1 depending on whether the view is closed or open.

In getView(), if convertView is null, you should create it depending on what getItemViewType() returns for that position. And if it's not null, you are guaranteed that it is one of the views that you created earlier for the same type.

And here is the point of crash which happens if your getItemViewType() returns arbitrary large numbers (I'm referring to sources for API 19):

// ListView.java
public void setAdapter(ListAdapter adapter) {
    ...
    // This is the only call to getViewTypeCount()
    mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    ...
}

// AbsListView.java
public void setViewTypeCount(int viewTypeCount) {
    ...
    // Here it creates the array of given size
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    ...
    mViewTypeCount = viewTypeCount;
    mScrapViews = scrapViews;
}

void addScrapView(View scrap, int position) {
    final int viewType = lp.viewType;
    ...
    // And here you get ArrayIndexOutOfBoundsException
    mScrapViews[viewType].add(scrap);
}
Comments