21

I have an app with tons of downloads and I'm receiving a lot of this error:

 16783         AndroidRuntime  E  java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
 16783         AndroidRuntime  E    at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:257)
 16783         AndroidRuntime  E    at java.util.ArrayList.get(ArrayList.java:311)
 16783         AndroidRuntime  E    at android.widget.HeaderViewListAdapter.isEnabled(HeaderViewListAdapter.java:16
                                  4)
 16783         AndroidRuntime  E    at android.widget.ListView.dispatchDrawWithExcessScroll_Default(ListView.java:3
                                  288)
 16783         AndroidRuntime  E    at android.widget.ListView.dispatchDraw(ListView.java:3029)
 16783         AndroidRuntime  E    at android.view.View.draw(View.java:6743)
 16783         AndroidRuntime  E    at android.widget.AbsListView.draw(AbsListView.java:2549)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.View.draw(View.java:6743)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.View.draw(View.java:6743)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
 16783         AndroidRuntime  E    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
 16783         AndroidRuntime  E    at android.view.View.draw(View.java:6743)
 16783         AndroidRuntime  E    at android.widget.FrameLayout.draw(FrameLayout.java:352)
 16783         AndroidRuntime  E    at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java
                                  :1885)
 16783         AndroidRuntime  E    at android.view.ViewRoot.draw(ViewRoot.java:1407)
 16783         AndroidRuntime  E    at android.view.ViewRoot.performTraversals(ViewRoot.java:1163)
 16783         AndroidRuntime  E    at android.view.ViewRoot.handleMessage(ViewRoot.java:1727)
 16783         AndroidRuntime  E    at android.os.Handler.dispatchMessage(Handler.java:99)
 16783         AndroidRuntime  E    at android.os.Looper.loop(Looper.java:123)
 16783         AndroidRuntime  E    at android.app.ActivityThread.main(ActivityThread.java:4627)
 16783         AndroidRuntime  E    at java.lang.reflect.Method.invokeNative(Native Method)
 16783         AndroidRuntime  E    at java.lang.reflect.Method.invoke(Method.java:521)
 16783         AndroidRuntime  E    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:8
                                  58)
 16783         AndroidRuntime  E    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
 16783         AndroidRuntime  E    at dalvik.system.NativeStart.main(Native Method)

As you can see in the stack trace, there is not one line with my code trace in it. To reproduce this, I luckily found an user with a Froyo (2.2 p7) and I just scrolled down one of the ListViews on the code. After some random time, it just froze with this exception. This happens every time at a different time.

It is a ListView with an EndlessAdapter behind it, just adding more and more rows. Apparently I get this error when I do the "over-scroll" but I cannot even think of a way to fix this. It is hard enough to reproduce, but with almost 200 users online at every time, if they keep getting this error they will end up not using the app anymore.

Any help would be appreciated.

EDIT: There has been a similar issue with someone else with the EndlessAdapter. http://groups.google.com/group/cw-android/browse_thread/thread/4739ce05742841da/af59c779e99f5e23?lnk=gst&q=index#af59c779e99f5e23

But it's not EndlessAdapter's fault. It's android's fault.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
Draiken
  • 3,805
  • 2
  • 30
  • 48
  • is this happening with Samsung fit phone? – om252345 Dec 08 '11 at 13:09
  • 1
    Related also to: http://stackoverflow.com/questions/1656593/android-endless-adapter – Gray Dec 08 '11 at 13:41
  • This answer is also interesting: http://stackoverflow.com/a/3383885/179850 – Gray Dec 08 '11 at 13:42
  • @om252345 yes actually I've managed to reproduce this on that exact phone – Draiken Dec 08 '11 at 14:06
  • You also get this issue when scrolling a list and then changing the adapters size to smaller that the current lists visible position before the fling has finished. Fix with `ListView.smoothScrollToPosition(...)` – Dori Jan 22 '14 at 15:12
  • @Dori nice! too bad I was on API 7 and this method was added on 8 :/ – Draiken Jan 23 '14 at 17:55
  • I find this problem in Nexus 5 with Android 6.0. I find this link http://stackoverflow.com/questions/25471000/headerviewlistadapter-randomly-crashes) works – chinaanihchen Nov 24 '15 at 07:11

3 Answers3

35

After a lot of time checking the android source code and not understanding this error I've finally cracked it.

The problem occurs with Samsung phones, they have a different implementation on the over-scroll functionality and that ended up throwing this exception as it tried to select a footer/header out of bounds (even when there is no footer view).

The solution I used is not pretty but it will stop this error from happening ever again.

class MyFixedListView extends ListView {
    @Override
    protected void dispatchDraw(Canvas canvas) {
        try {
            super.dispatchDraw(canvas);
        } catch (IndexOutOfBoundsException e) {
            // samsung error
        }
    }
}

Now I use this ListView implementation and the error is gone.

I really hope this helps anyone using endless adapters.

Zaz Gmy
  • 4,236
  • 3
  • 19
  • 30
Draiken
  • 3,805
  • 2
  • 30
  • 48
  • My apologies for not responding sooner to your issue -- I have been away from teh Internets for several hours. What specific model(s) of phone were giving you the exception? – CommonsWare Dec 08 '11 at 19:53
  • Samsung ones with Froyo or lower – Draiken Dec 08 '11 at 21:33
  • 4
    This also occurs on a Nexus S running a early release of 4.0, so it's not limited to Froyo or lower. – Quentamia Apr 12 '12 at 18:47
  • @QuentonJones yeah apparently it's all the samsung phones. Since we cannot fix the issue for them, we just have to "ignore" the error :/ – Draiken Apr 18 '12 at 15:38
  • wow, I got this same error in my Galaxy Nexus, thanks to you for finding the source of the error. I was completely lost looking for where it came. – giorgiline Sep 25 '12 at 22:41
  • I've seen it on the S3 and, bizarrely enough, on a Motorola Xoom. – kodi Oct 17 '12 at 22:36
  • Also occurs on the Nexus One – Till - Appviewer.io Dec 03 '12 at 22:11
  • Thanks for sharing, could you please be more specific about the code you have posted? Is "dispatchDraw" used in your app elsewhere, or is it just used for the fix? where should this code be added exactly? – Don Mar 11 '13 at 09:34
  • @Kintaro as the code shows, I extended the `ListView` to fix this problem. So everywhere you don't want this problem to happen, you should use your subclass instead of the `ListView`. – Draiken Mar 11 '13 at 17:45
  • I don't know if this is connected to EndlessAdapter at all. I've had lots of users crashlog report a similar backtrace for just normal Android ListView and header/footers: http://pastebin.com/agXxYKMk – Espen Riskedal Aug 26 '13 at 17:46
  • @EspenRiskedal it isn't, as the answer says :) It has to do with bad implementation of the android system. For me it happened on some samsung phones, but any fork of android might have the same bug. I'll edit the question to not point the finger at EndlessAdapter :) – Draiken Aug 27 '13 at 17:23
  • This happened on Samsung Rubgy Pro OS v4.1.2 too! – Ali Mehrpour Mar 02 '15 at 18:51
  • I had an issue with app crash because of LoadmoreGridView render when I open activity for many times. By this code, I solved my problem. – Mohit Saxena Apr 27 '17 at 19:05
4

Same issue happens in my app when testing on the ICS tablets. It happens only when I have 11 elements in the list, when EndlessAdapter loads by 10 items at time.

This is a race condition issue. The problem comes from the keepOnAppending flag update on the AsyncTask thread. While ListView accesses getCount on the UI thread andkeepOnAppending is set to true, later when it calls getView in the same UI thread andkeepOnAppending is already reset in the doInBackground:

@Override
public int getCount() {
    if (keepOnAppending.get()) {
        return (super.getCount() + 1); // one more for
                                        // "pending"
    }

    return (super.getCount());
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (position == super.getCount() && keepOnAppending.get()) {
        if (pendingView == null) {
            pendingView = getPendingView(parent);

            executeAsyncTask(new AppendTask());
        }

        return (pendingView);
    }
    return (super.getView(position, convertView, parent));
}

class AppendTask extends AsyncTask<Void, Void, Exception> {
    @Override
    protected Exception doInBackground(Void... params) {
        Exception result = null;

        try {
            keepOnAppending.set(cacheInBackground());
        } catch (Exception e) {
            result = e;
        }

        return (result);
    }

    @Override
    protected void onPostExecute(Exception e) {
        if (e == null) {
            appendCachedData();
        } else {
            keepOnAppending.set(onException(pendingView, e));
        }

        pendingView = null;
        notifyDataSetChanged();
    }
}

As a fix, I changed the code of the AppendTask so that it updates keepOnAppending on the UI thread, only when new items added to the adapter.

Here is the code:

class AppendTask extends AsyncTask<Void, Void, AppendTask.Result> {
    class Result {
        boolean status;
        Exception ex;

        public Result(Exception ex) {
            this.ex = ex;
        }

        public Result(boolean result) {
            this.status = result;
        }
    }

    @Override
    protected AppendTask.Result doInBackground(Void... params) {
        try {
            return new Result(cacheInBackground());
        } catch (Exception e) {
            return new Result(e);
        }
    }

    @Override
    protected void onPostExecute(AppendTask.Result result) {
        if (result.ex == null) {
            appendCachedData();
            keepOnAppending.set(result.status);
        } else {
            keepOnAppending.set(onException(pendingView, result.ex));
        }

        pendingView = null;
        notifyDataSetChanged();
    }
}
Kushal
  • 8,100
  • 9
  • 63
  • 82
  • I think this is a different problem with the same error generated. When I faced this bug, it ONLY happened in one Froyo Samsung device, and I know for a fact they wrap the `ListView` with their own class and that generated the error. – Draiken Jul 13 '12 at 11:51
  • I was having a similar race condition. My solution was to hide the list and show a loading indicator and then display the list after data loading is finished. – Alex Mar 26 '15 at 10:49
0

This reappeared for me with ListView and SimpleCursorAdapter when updating from different thread. I believe this is due to race condition and multithreading and that it can happen with all adapters. Relevant part to my solution:

public class MyAdapter extends *Adapter {
  /**
  * We need this because even though we publish `notifyDataSetChanged()` on main thread, for 
  * example `isEnabled()` might already be there and it will run before it. Note that this 
  * should be set asap when underlying data changes. 
  */
  private boolean notifyingDataChange = false;

  /**
  * Note that you might need to adapt code and only update notifyDataChange
  * when underlying data changes
  */
  public void notifyDataSetChangedOnMain(@NonNull Context context) {
    notifyingDataChange = true;

    ContextCompat.getMainExecutor(context).execute(() -> {
      notifyDataSetChanged();
      notifyingDataChange = false;
    });
  }

  @Override
  public boolean isEnabled(int position) {
    if (notifyingDataChange) {
      return false;
    }

    return super.isEnabled(position); // or other logic if needed
  }
}
primus
  • 30
  • 4