6

I've a splashscreen Fragment that is registered to event bus:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

If the screen goes autolock (or any other event that could call onStop), the container activity goes onStop and the fragment is no more capable to receive the (network) event. I'm thinking about moving "unregister" logic to onDestroy method. Is it a good idea?

Jumpa
  • 4,319
  • 11
  • 52
  • 100

2 Answers2

11

You can move the event bus events to onStart() and onStop(), BUT you should know that certain methods cannot be called after onSaveInstanceState() (for example, dismissing a DialogFragment crashes you out).

So while this example was for Otto, I switched it to EventBus, and I personally have a wrapper that stores events while the application is paused.

public enum SingletonBus {
    INSTANCE;

    private static String TAG = SingletonBus.class.getSimpleName();

    private final Vector<Object> eventQueueBuffer = new Vector<>();

    private boolean paused;

    public <T> void post(final T event) {
            if (paused) {
                eventQueueBuffer.add(event);
            } else {
                EventBus.getDefault().post(event);
            }
    }

    public <T> void register(T subscriber) {
        EventBus.getDefault().register(subscriber);
    }

    public <T> void unregister(T subscriber) {
        EventBus.getDefault().unregister(subscriber);
    }

    public boolean isPaused() {
        return paused;
    }

    public void setPaused(boolean paused) {
        this.paused = paused;
        if (!paused) {
            Iterator<Object> eventIterator = eventQueueBuffer.iterator();
            while (eventIterator.hasNext()) {
                Object event = eventIterator.next();
                post(event);
                eventIterator.remove();
            }
        }
    }
}

Then in a BaseActivity of sorts,

public class BaseActivity extends AppCompatActivity {
     @Override
     public void onPostResume() {
          super.onPostResume();
          SingletonBus.INSTANCE.setPaused(false);
     }

     @Override
     public void onPause() {
          SingletonBus.INSTANCE.setPaused(true);
          super.onPause();
     }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Nice! Are you talking about IllegalStateException for committing fragment transaction inside lifecycle methods? – Jumpa Jul 19 '15 at 10:01
  • 1
    Yes. That's a total bitch, I was surprised when I ran into it. So I allow the transmission of events only when the app is not paused. – EpicPandaForce Jul 19 '15 at 10:02
  • That is because the system can brutally kill your process after `onSaveInstanceState()`, so you could lose the transaction just committed. Btw nice code, but why `Vector`? – Gil Vegliach Jul 19 '15 at 10:12
  • @Gil because Vector methods are synchronized. It's actually based on a related question: http://stackoverflow.com/a/8122789/2413303 – EpicPandaForce Jul 19 '15 at 10:17
  • @EpicPandaForce: I thought so, anyway you need more synchronization to make it thread safe, e.g. post() is not atomic, paused flag is not "memory-consistent", etc. – Gil Vegliach Jul 19 '15 at 10:21
  • 1
    @Gil hmm, I haven't run into issues with that yet, but I might need to synchronize it a bit better, then.... I seem to have forgotten the `volatile` flag for `paused`, that is certain. Thanks for the input. Also, I realized on my whipped-up example that it should set `paused` to `false` only in `onPostResume()`, so I changed that accordingly. – EpicPandaForce Jul 19 '15 at 10:23
  • I've implemented your solution in my project and it seems to work good! In general should I register/unregister the bus inside the "container" activity or inside each fragment? Anyway I used onCreate and onDestroy callback. – Jumpa Jul 22 '15 at 11:18
  • I personally would do it in the fragment lifecycle callbacks. If you encounter any issues (I hope you won't :P), don't hesitate to tell me, because I'll run into it later too! – EpicPandaForce Jul 22 '15 at 11:23
  • Well I think I just encountered an issue...if I call startActivity inside an Event callback, then the Activity is paused, your event bus saves it, and the callback is executed twice. How can I solve it? – Jumpa Jul 24 '15 at 15:09
  • Are you sure that you didn't send the event twice? It should only be stored while the bus is paused, and that happens only AFTER startActivity is called in the event callback. – EpicPandaForce Jul 24 '15 at 15:36
  • 1
    Nevermind, it was my fault; another activity was intercepting the same event. I patched that adding a "source = class.getSimpleName" to each event: this way I can identify the caller. – Jumpa Jul 25 '15 at 09:37
2

I recently had a similar issue. First I used Otto and then switched to greenrobot's EventBus. With EventBus you can register your objects sticky. EventBus.getDefault().registerSticky(this) That is, this object will automatically receive every sticky event it is waiting for after registration. These events then have to be posted sticky. (EventBus.getDefault().postSticky(event))

Consider to remove the sticky events if you don't need them anymore (EventBus.getDefault().removeSticky(Event.class)). So if you want to receive this event only once you should remove it. Maybe this approach might fit your needs.

Chris Fox
  • 76
  • 1
  • 6