ArrayIndexOutOfBoundsException when using ListAdapter

gyozo kudor picture gyozo kudor · Apr 14, 2011 · Viewed 8k times · Source

I'm using a custom ListAdapter for a ListView. I don't change anything dynamically (view count, view types, enabled/disabled, I read other posts but they changed something from code). I have to do the following to trigger the exception. Scroll down to the bottom of the list and after that scroll up again. At the start of the scroll-up animation I get this exception.

04-14 09:41:43.907: ERROR/AndroidRuntime(400): FATAL EXCEPTION: main
04-14 09:41:43.907: ERROR/AndroidRuntime(400): java.lang.ArrayIndexOutOfBoundsException
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:4540)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.widget.AbsListView.trackMotionScroll(AbsListView.java:3370)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.widget.AbsListView.onTouchEvent(AbsListView.java:2233)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.widget.ListView.onTouchEvent(ListView.java:3446)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.View.dispatchTouchEvent(View.java:3885)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:903)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1691)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1125)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.app.Activity.dispatchTouchEvent(Activity.java:2096)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1675)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.ViewRoot.deliverPointerEvent(ViewRoot.java:2194)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1878)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.os.Handler.dispatchMessage(Handler.java:99)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.os.Looper.loop(Looper.java:123)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at android.app.ActivityThread.main(ActivityThread.java:3683)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at java.lang.reflect.Method.invokeNative(Native Method)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at java.lang.reflect.Method.invoke(Method.java:507)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
04-14 09:41:43.907: ERROR/AndroidRuntime(400):     at dalvik.system.NativeStart.main(Native Method)

Down below is the complete implementation.

public class CompanyDetailsAdapter implements ListAdapter {

  private Context context;
  private LayoutInflater inflater;

  private static final class ViewType {
    private static final int HEADER = 1;
    private static final int TEXT = 2;
    private static final int ADDRESS = 3;
    private static final int REVIEWS = 4;
    private static final int PRODUCTS = 5;
  }

  //all the cells in the order they appear
  private static final class ViewPositions {
    private static final int CompanyNameHeader = 0;
    private static final int CompanyName = 1;
    private static final int ContactHeader = 2;
    private static final int Phone = 3;
    private static final int Website = 4;
    private static final int AddressHeader = 5;
    private static final int Address = 6;
    private static final int DescriptionHeader = 7;
    private static final int Description = 8;
    private static final int ReviewsHeader = 9;
    private static final int Reviews = 10;
    private static final int ProductsHeader = 11;
    private static final int Products = 12;
  }

  //list of cells by types
  private static final int[] viewTypes = {
  ViewType.HEADER,      //0
  ViewType.TEXT,            //1
  ViewType.HEADER,      //2
  ViewType.TEXT,            //3
  ViewType.TEXT,            //4
  ViewType.HEADER,      //5
  ViewType.ADDRESS,     //6
  ViewType.HEADER,      //7
  ViewType.TEXT,            //8
  ViewType.HEADER,      //9
  ViewType.REVIEWS,     //10
  ViewType.HEADER,      //11
  ViewType.PRODUCTS     //12
  };

  //headers
  private static final int[] headerPositions = {
  ViewPositions.CompanyNameHeader,
  ViewPositions.ContactHeader,
  ViewPositions.AddressHeader,
  ViewPositions.DescriptionHeader,
  ViewPositions.ReviewsHeader,
  ViewPositions.ProductsHeader
  };

  //header resources for position where needed
  private static final int[] headerResources = {
  R.string.companyName,
  0,
  R.string.contact,
  0,
  0,
  R.string.address,
  0,
  R.string.description,
  0,
  R.string.reviews,
  0,
  R.string.products,
  0
  };

  //regular texts
  private static final int[] textPositions = {
  ViewPositions.CompanyName,
  ViewPositions.Phone,
  ViewPositions.Website,
  ViewPositions.Description,
  };

  public CompanyDetailsAdapter(Context context) {
    this.context = context;
    this.inflater = LayoutInflater.from(this.context);
  }

  public int getCount() {
    return viewTypes.length;
  }

  public Object getItem(int position) {
    return null;
  }

  public long getItemId(int position) {
    return position+1;
  }

  public int getItemViewType(int position) {
    return viewTypes[position];
  }

  public View getView(int position, View convertView, ViewGroup parent) {

    if (arrayContains(headerPositions, position)) {
      LinearLayout layout = null;
      TextView txtHeader = null;
      if (convertView == null) {
        layout = (LinearLayout)inflater.inflate(R.layout.header_listview,parent, false);
      } else {
        layout = (LinearLayout)convertView;
      }
      txtHeader = (TextView)layout.findViewById(R.id.txtHeader);
      String value = context.getText(headerResources[position]).toString();
      txtHeader.setText(value);
      return layout;
    } else if (arrayContains(textPositions, position)) {
      TextView txtText = null;
      LinearLayout layout = null;
      Company c = Company.getSelectedCompany();
      if (convertView == null) {
        layout = (LinearLayout)inflater.inflate(R.layout.default_text,parent, false);
      } else {
        layout = (LinearLayout)convertView;
      }
      txtText = (TextView)layout.findViewById(R.id.txtNormalText);
      switch (position) {
        case ViewPositions.CompanyName:
          txtText.setText(c.getName());
          break;
        case ViewPositions.Phone:
          txtText.setText(c.getPhone());
          break;
        case ViewPositions.Website:
          txtText.setText(c.getWebsiteString());
          break;
        case ViewPositions.Description:
          txtText.setText(c.getDescription());
          break;
      }
      return layout;
    } else {
      View v = null;
      //LinearLayout layout = null;
      Company company = Company.getSelectedCompany();
      switch (position) {
        case ViewPositions.Address:
          if (convertView == null)
            v = (LinearLayout)inflater.inflate(R.layout.adress, parent,false);
          else
            v = (LinearLayout)convertView;

          TextView txtAddress = (TextView)v.findViewById(R.id.txtAddress);
          txtAddress.setText(String.format("%s %s %s", company.getCity(),
                                           company.getStreet(), company.getPostalCode()));
          break;
        case ViewPositions.Products:
          if (convertView == null)
            v = (LinearLayout)inflater.inflate(R.layout.products, parent,false);
          else
            v = (LinearLayout)convertView;

          v = (LinearLayout)inflater.inflate(R.layout.products, parent,false);
          TextView txtNumProducts = (TextView)v.findViewById(R.id.txtNumProducts);
          txtNumProducts.setText(String.valueOf(company.getNrProducts()));
          break;
        case ViewPositions.Reviews:
          if (convertView == null)
            v = (LinearLayout)inflater.inflate(R.layout.reviews, parent,false);
          else
            v = (LinearLayout)convertView;

          v = (LinearLayout)inflater.inflate(R.layout.reviews, parent,false);
          TextView txtNumReviews = (TextView)v.findViewById(R.id.txtNumReviews);
          txtNumReviews.setText(String.valueOf(company.getNrReviews()));
          RatingBar rating = (RatingBar)v.findViewById(R.id.ratAvg);
          rating.setRating(company.getAvgRating());
          break;
      }
      return v;
    }
  }

  public int getViewTypeCount() {
    return 5;
  }

  public boolean hasStableIds() {
    return true;
  }

  public boolean isEmpty() {
    return false;
  }

  public void registerDataSetObserver(DataSetObserver arg0) {
  }

  public void unregisterDataSetObserver(DataSetObserver arg0) {
  }

  public boolean areAllItemsEnabled() {
    return false;
  }

  public boolean isEnabled(int position) {
    if (arrayContains(headerPositions, position)) return false;
    return true;
  }

  private Boolean arrayContains(int[] array, int element) {
    for (int i=0; i<array.length; i++) {
      if (array[i] == element) return true;
    }
    return false;
  }


}

I attached the sources to the Android 2.3.3 library from here. I didn't find out anything else that the stack trace didn't show me. As you all know the ListView recycles views. There is a collection of some kind that holds these recycled views. When a view dissappears it is added there (I think). This happens at at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:4540) Somewhere here it gives me an exception that something is out of bounds. I don't have the slightest clue what. But I don't have the patience to debug another 1000 lines of code so I think I will use a ScrollView for now.


It seems it is this line: (AbsListView.class:4540 mScrapViews[viewType].add(scrap); mScrapViews is of type private ArrayList<View>[] mScrapViews;. It is an array of lists of type View. I can try to watch viewType and scrap variables but it says errors_during_evaluation. Probably too dumb to evaluate.

Answer

gyozo kudor picture gyozo kudor · Apr 15, 2011

Found the problem. View types had to begin at 0, mine starts at 1.

Android is using view types I provided to access an array by index somewhere. SomeArraySomewhere[myViewType] The size of this array probably equals the number of view types. So this means I have to start my view types at 0.