What is triggering this Exception instance: "java.lang.IllegalArgumentException: The observer is null." and how could it be avoid?
Asked Answered
B

2

9

I'm getting this exception when returning to the original ListActivity after opening an new activity with the content of the item selected by the user. It only occurs on Ice Cream Sandwich.

This is the trace:

 java.lang.IllegalArgumentException: The observer is null.
    at android.database.Observable.unregisterObserver(Observable.java:59)
    at android.widget.BaseAdapter.unregisterDataSetObserver(BaseAdapter.java:42)
    at android.widget.AbsListView.onDetachedFromWindow(AbsListView.java:2373)
    at android.view.View.dispatchDetachedFromWindow(View.java:9756)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2274)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
    at android.view.ViewRootImpl.dispatchDetachedFromWindow(ViewRootImpl.java:2227)
    at android.view.ViewRootImpl.doDie(ViewRootImpl.java:3679)
    at android.view.ViewRootImpl.die(ViewRootImpl.java:3667)
    at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:320)
    at android.view.WindowManagerImpl$CompatModeWrapper.removeViewImmediate(WindowManagerImpl.java:139)
    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3144)
    at android.app.ActivityThread.access$1200(ActivityThread.java:122)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1179)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4340)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
    at dalvik.system.NativeStart.main(Native Method)

None of my code is mentioned in the stack so it's really puzzling me and I can't event do a dirty catch :/

I am registering an anonymous DataSetObserver only when the activity is created. Also the activity is embedded inside an ActivityGroup (which is deprecated in 4.0 but should still suport it, I was hoping).

Has anybody had this problem with the new OS?

Thanks in advance.


Update:

Ok, think I found the source of the problem although not sure how to resolve it.

Inside AbsListView.onDetachedFromWindow() We have this:

if (mAdapter != null) { // Android code added on ICS
    mAdapter.unregisterDataSetObserver(mDataSetObserver);
    mDataSetObserver = null;
}

Which, once the observer is unregistered, it gets nullified. The problem comes that, for some reason, in ICS it gets called twice. I think it's a bit silly to check for null parameters inside a delete operation as they do in the Observable class:

public void unregisterObserver(T observer) { // Android code
    if (observer == null) {
        throw new IllegalArgumentException("The observer is null.");
    }
    synchronized(mObservers) {
        int index = mObservers.indexOf(observer);
        if (index == -1) {
            throw new IllegalStateException("Observer " + observer + " was not registered.");
        }
        mObservers.remove(index);
    }
}

Why don't they just ignore it ¬¬ They could just do this and would work as well (or better):

public void unregisterObserver(T observer) { // Android code
    synchronized(mObservers) {
        mObservers.remove(observer);
    }
}
Brythonic answered 14/12, 2011 at 16:48 Comment(3)
can you post your activity code and when you say returning to do you mean pressing the back button?Darill
Yeah, by returning I mean pressing the back button. Here is the code for the activity (pastebin.com/68BzJcc1), but as I mention, there is non of my code shown in the stack.Brythonic
I've filled a bug report: code.google.com/p/android/issues/detail?id=22946 Although there must be a solution :/Brythonic
V
11

This problem was introduced in Android 4.0.3, and the Observable class was changed to throw an exception when an observer was released more than once. It has been reported as a bug and can be read here: http://code.google.com/p/android/issues/detail?id=22946.

The easiest way to work around this problem is to wrap the underlying adapter and avoid multiple releases.

@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
  if (observer != null) {
    super.unregisterDataSetObserver(observer);
  }
} 

But that will not work in all cases, e.g. ExpandableListView has an internal adapter that cannot be accessed. An alternative solution here is to wrap the ExpandableListView and catch the exception. This solution worked for me, and I have yet not found any side effects.

public class PatchedExpandableListView extends ExpandableListView {

  public PatchedExpandableListView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  protected void onDetachedFromWindow() {
    try {
      super.onDetachedFromWindow();
    } catch(IllegalArgumentException iae) {
      // Workaround for http://code.google.com/p/android/issues/detail?id=22751
    }
  }
}
Viva answered 7/2, 2012 at 9:34 Comment(0)
B
3

I made the silly mistake of thinking that none of my classes were mentioned in the trace, but LoadingDataView is one of them. It doesn't show in the original trace but another that was related.

Inside that class there is an anonymous ArrayAdapter that was where the incident is happening so I added this as a work around:

@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
    if (observer != null) {
        super.unregisterDataSetObserver(observer);
    }
}

And it seems to work now although I'm still not sure why this method was called twice.

Although, for now on I'm going to use Fragments as much as I can ;)

Brythonic answered 15/12, 2011 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.