7

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

mhasan
  • 3,703
  • 1
  • 18
  • 37
K3NN3TH
  • 1,458
  • 2
  • 19
  • 31
  • 1
    Paste your adapter code – bhargavg Sep 09 '14 at 16:20
  • Even though not related to the proble, why are you returning `false` from `hasStableIds()`? – bhargavg Sep 09 '14 at 16:32
  • @bhargavg i was trying to get animation to work on items and be able to retain their id, was toggling it while trouble shooting. – K3NN3TH Sep 09 '14 at 16:56
  • Did you post enough of the logcat? As i miss your .java file with the offending code. Further `length=12 index=12` should tell you something of the cause. Why is getViewTypeCount equal to getCount? – greenapps Sep 09 '14 at 17:01
  • @greenapps I override getViewTypeCount so that other items do not open when a user clicks the item. It was the only solution I could find for the problem. – K3NN3TH Sep 09 '14 at 17:48
  • `If I do not override it when a user opens an item it will open others also.`. ? Why would that happen? What did you/the user do to expand an item? Click it? – greenapps Sep 09 '14 at 17:58
  • @greenapps the user clicks an item and it returns the view clicked. I then expand that view – K3NN3TH Sep 09 '14 at 18:07
  • ??? Now is the user clicking an item or a view? And who is returning a view? Returning a view that is clicked? ?? – greenapps Sep 09 '14 at 18:10
  • @greenapps ya, you can see it in the adapter code method OnItemClickListener, it returns the view clicked – K3NN3TH Sep 09 '14 at 18:14
  • Sorry but i do not see that it returns something. – greenapps Sep 09 '14 at 18:31

3 Answers3

14

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);
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • I am adding views later, so i need to tell getItemViewType() of the additional views added. The error is random also, some times im able to add 20 or more and no error. other times i add 2 and get an error. – K3NN3TH Sep 09 '14 at 17:50
  • What do you mean with adding views? You can only add to the array list. And are they 'views' of another type? You did not make clear why you fiddle around with view type count where you onky have one view type. – greenapps Sep 09 '14 at 17:56
  • @K3NN3TH Again, you have views of only one type, therefore `getViewTypeCount()` should return 1 no matter how many views you have. – Anton Savin Sep 09 '14 at 17:58
  • @AntonSavin I only have views of one type but i need each one to be treated as its own view. OR ELSE, if i have 200 items in the list and you open item 1 it will open item 12, 24, and so on. So when a user opens item 1 then scrolls other items will be open, that is why I am overriding it and not using view-recycling. – K3NN3TH Sep 09 '14 at 18:04
  • @K3NN3TH It doesn't work like this, I wrote in my answer the exact reason for crash. – Anton Savin Sep 09 '14 at 18:06
  • If it opens also 12,24 and so on then your code is wrong. Try to find the erronous code and repair it instead of adding wrong hacks. – greenapps Sep 09 '14 at 18:06
  • @greenapps it opens the other because of view-recycling, depending on how many closed items fit on your screen determines what views will be open. so if 6 closed items fit on your screen and you open item 1 then item 7 will open also since it is the item 1 from the view-recycling point – K3NN3TH Sep 09 '14 at 18:10
  • @K3NN3TH use `ExpandableListView` – Anton Savin Sep 09 '14 at 18:15
  • It has to do with view recycling i agree. But there is no reason that another view should open also. You did something wrong that this happens. – greenapps Sep 09 '14 at 18:16
  • @greenapps all i am doing is returning that view than expanding the view that is returned. Ill show you animation code, it is targeting the view returned yet it opens other views off the screen – K3NN3TH Sep 09 '14 at 18:19
  • @AntonSavin expandablelistview is not what i am looking for, that is something totally different – K3NN3TH Sep 09 '14 at 18:21
  • I think this is a really great explanation. (: – cokeby190 Jan 22 '15 at 07:18
  • if you are going to be adding views dynamically, then probably best you set the getViewTypeCount to a number large enough that can hold all possible views you could add later, because the getViewTypeCount is called once in setAdapter and the getItemViewType should be greater or equal to the getViewTypeCount(if i remember correctly). Hope this helps. – irobotxx Mar 19 '15 at 12:01
  • Overriding getItemViewType() was the one doing it for me! Thanks! – Gmeister4 Nov 06 '15 at 12:17
1

This also happens when your getItemViewType returns integers exceeding the size of getViewTypeCount, so they are like indices. E.g. getViewTypeCount 2 requires getItemViewType of 0 and 1.

Till - Appviewer.io
  • 4,529
  • 1
  • 31
  • 35
0

You think that the index out of bounds happens with notify dataset changed. I agree. For instance when you delete an item. All the on item click listeners following that item will have the wrong mPosition value then.

greenapps
  • 11,154
  • 2
  • 16
  • 19