1

I recieve exception while manipulating with navigation forward and backward from FragmentActivity, containing ListView with data loaded from custom Loader (it is loading from local sqlite database), and do not know how to deal with it...

Here is a stacktrace:

Uncaught exception: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.PriceTracerActivity}: java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.CartFragmentActivity}: java.util.ConcurrentModificationException
java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.PriceTracerActivity}: java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.CartFragmentActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3106)
    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3171)
    at android.app.ActivityThread.access$2100(ActivityThread.java:132)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1071)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:150)
    at android.app.ActivityThread.main(ActivityThread.java:4293)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.CartFragmentActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3106)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:2994)
    at android.app.LocalActivityManager.dispatchDestroy(LocalActivityManager.java:625)
    at android.app.ActivityGroup.onDestroy(ActivityGroup.java:85)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3088)
    ... 11 more
Caused by: java.util.ConcurrentModificationException
    at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:576)
    at android.database.DataSetObservable.notifyInvalidated(DataSetObservable.java:42)
    at android.widget.BaseAdapter.notifyDataSetInvalidated(BaseAdapter.java:54)
    at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:352)
    at com.snyer.bestprice.CartFragmentActivity.onLoaderReset(CartFragmentActivity.java:412)
    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.destroy(LoaderManager.java:337)
    at android.support.v4.app.LoaderManagerImpl.doDestroy(LoaderManager.java:773)
    at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:318)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3088)
    ... 15 more

Exceptions occures at line mCartCursorAdapter.swapCursor(null); of this code-fragment (CartFragmentActivity.java:412):

public void onLoaderReset(Loader<Cursor> loader) {
    switch (loader.getId()) {
    case LOADER_CART_LIST_ID:
        mCartListCursorAdapter.swapCursor(null);
        break;

    case LOADER_CART_CONTENTS_ID:
        mCartCursorAdapter.swapCursor(null);
        break;
    }
}

What can be done here to remove this exception?

Prizoff
  • 4,486
  • 4
  • 41
  • 69
  • Did you find a working solution or the cause? I got the exact same problem. Have you read: http://stackoverflow.com/questions/8189466/java-util-concurrentmodificationexception ? – phlebas Nov 02 '12 at 20:54
  • @phlebas I've left this problem for some time in the nearest future... Was busy with some other bugs/doings in the project, but this is the problem to which I will come back surely. – Prizoff Nov 02 '12 at 21:56
  • From what I can tell this only occurs on devices running 2.3.x . Can you confirm that observation? – phlebas Nov 05 '12 at 12:32
  • @phlebas hmmm.. I can't say for sure on which exact of the versions of Android it does fail.. I've tested both on the phone (CyanogenMod Android 2.3.7) and on the emulator (Android 4.0 x86 version as VMWare image)... But it seems it fails on both "devices" (but can't say for sure about it.. just do not remember) – Prizoff Nov 05 '12 at 22:06

3 Answers3

4

The sdk source gives a good hint as a comment:

In Android 4.0.4 it is:

public void notifyInvalidated() {
    synchronized(mObservers) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onInvalidated();
        }
    }
}

In earlier versions it was:

 synchronized (mObservers) {
        for (DataSetObserver observer : mObservers) {
            observer.onInvalidated();
        }
    }

The 4.0.4 source has the following comment regarding the same changes in onChange method:

// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.

I am not aware of manipulating the ArrayList of observers in any illegal way though. Since this is solved in later APIs and I can't think of any impact on my App I think I am simply going to ignore the exception with a log entry or similar.

phlebas
  • 1,210
  • 2
  • 13
  • 24
0

It may be that onLoaderReset() is being called on a background thread by the Loader. Try using runOnUiThread() or similar mechanisms to move your swapCursor() call to the main application thread.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
0

The fastest way it is to copy DataSetObservable from "right" version of the sdk to your app and use it. DataSetObservable should looks like this:

public class DataSetObservable extends Observable<DataSetObserver> {
/**
 * Invokes {@link DataSetObserver#onChanged} on each observer.
 * Called when the contents of the data set have changed.  The recipient
 * will obtain the new contents the next time it queries the data set.
 */
public void notifyChanged() {
    synchronized(mObservers) {
        // since onChanged() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }
}

/**
 * Invokes {@link DataSetObserver#onInvalidated} on each observer.
 * Called when the data set is no longer valid and cannot be queried again,
 * such as when the data set has been closed.
 */
public void notifyInvalidated() {
    synchronized (mObservers) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onInvalidated();
        }
    }
} 

In your case you should copy BaseAdapter from the source code too. And change in it android DataSetObservable to your app DataSetObservable.

jumper0k
  • 2,166
  • 2
  • 22
  • 31