IllegalArgumentException with Otto Event bus in Fragment instance
Asked Answered
R

1

14

I am using Otto Event bus to subscribe to certain events in a ListFragment. The bus instance is stored and created in an subclass of Application, in other words, it bus should work as a singleton. It seems like this is not a case...

The fragment is registering to the bus in onActivityCreated(Bundle) and unregistering in onDestroy(). This does not work as it should. I have gotten several crash reports from devices where the app crashes when calling unregister() (java.lang.IllegalArgumentException: Missing event handler for an annotated method...). This exception is only thrown if unregister() is called before any call to register(), or if unregister() is called twice. This may only happen if...

  • onActivityCreated(Bundle) is not called before onDestroy().
  • onDestroy() is called twice.
  • The Application instance is recreated between between the call to onActivityCreated(Bundle) and onDestroy().

My application class:

public class App extends Application {

    private static App sInstance;

    private Bus bus;

    public static App getInstance() {
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        bus = new Bus(ThreadEnforcer.ANY);
    }

    public Bus getEventBus() {
        return bus;
    }

}

The Fragment class:

public class MyFragment extends ListFragment {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        App.getInstance().getEventBus().register(this);
    }

    @Subscribe
    public void onEvent(MyEvent event) {
        ....
    }

    @Override
    public void onDestroy() {
        App.getInstance().getEventBus().unregister(this);
        super.onDestroy();
    }
}

UPDATE:

I left out an important detail; the fragments were used in a ViewPager. They are instantiated on demand as the users slides between the pages in the ViewPager. This minor detail seems alter the fragments life-cycle on some devices: onActivityCreated() is never called for fragments initiated after the ViewPager is created.

Rainie answered 30/10, 2013 at 19:59 Comment(1)
Why not call it in onCreate() / onDestroy()? They're paired up.Villanueva
C
38

I had same issue. Instance stayed registered in the bus in some cases. A reliable solution is to use onStart()/onStop() methods to register/unregister receivers. This is what Square guys suggest too. They explain it like this. If activity is in background, it does not need to refresh UI anyway, because UI is not visible. Once activity comes foreground, it will receive update and refresh UI.

Update: as mentioned in the comments, registering / unregistering in onResume()/onPause() can cause some undesired effects in certain cases like if there is a dialog shown over your activity, then activity gets paused and is not able to receive events anymore.

Chengteh answered 1/11, 2013 at 23:40 Comment(9)
The problem is: What happens if fragment is waiting for background task, as getting a list of items from the server. If the user leaves the fragment before the list has been drawn, you'll never get the items because fragment has unregistered the bus, right?Aranyaka
@m3n0R Right. If fragment in your example gets stopped and destroyed, then result won't be dispatched to subscribers. If this is not a desired behavior, you need to use a retained fragment, which will live as long as long your activity lives.Chengteh
I'm using it. I'm used to register bus in onActivityCreated and unregister it in onDestroy methods within my fragment. Trying to improve gpu performance, instead of adding fragments to content (I don't have any problems) I replace fragments, so I get the bus problem when I pop up the last fragment replaced, because it hasn't passed through onDestroy method Can u get what I mean?Aranyaka
@m3n0R You can use onStart()/onStop() as suggested and there will be no issues.Chengteh
Yep, thx. I think the best option for my situation, is having registering/unregistering in onActivityCreated/onDestroyView methods, because the back stack ^_^Aranyaka
Apart from special purpose, don't register()/unregister() Otto during respective onResume()/onPause() with fragment. I found a loophole. If a dialog is shown over the fragment that supposedly receives bus events, the onPause() on the fragment will get called and unexpectedly unregister the Otto.Delitescence
@ThuyTrinh thanks for letting know this. I updated the answer.Chengteh
@ThuyTrinh Isn't that explicitly the purpose of onPause?Amphiarthrosis
I had same error as I was calling register and unregister on Fragment and on Activity, on which the Fragment was placed. So I shifted register and unregister to Fragment only i.e. removed Activity 's register and unregisterTrack

© 2022 - 2024 — McMap. All rights reserved.