One common pattern I see very often in Android development is the use of event buses. Libraries like Otto and EventBus are often used to remove boilerplate listeners that require tunneling code through many layers of hierarchy. Although an event bus might seem convenient at first, they quickly devolve into a mess of tangled events that are incredibly hard to follow and even more difficult to debug.
Event busses are often sold as allowing you to loosely couple together components, but in reality they give you the confusion of loose coupling and the downsides that come with tight coupling.
One common pitfall is nesting events. This might seem easy to avoid, just don’t send events in event subscribers, but often times it’s not that clear. A subscriber might call a method, which calls another method, which indirectly fired off another event. These tangled balls of events are incredibly complex and hard to debug.
Facebook’s Flux architecture is somewhat event driven, but they explicitly disallow sending nested events. I wish Otto and EventBus could detect this.
Treating Producers Like Synchronous Getters
(I don’t think this is relevant for GreenRobot’s EventBus.)
This is a another common pattern that’s incredibly hard to undo in a larger codebase. Often times many activities or fragments will assume events are produced as soon as that component subscribes to the event bus. They’ll set a field based on the event, and then proceed to use that field in other lifecycle methods with the assumption that it is not null.
When you do this, you’re making some very big assumptions about how these events are being sent – and none of these assumptions are really guaranteed or even clear based on the API, they are all implicit.
What happens when you refactor your code that produces the event to act a little differently? There’s a strong possibility that the person refactoring the event producing code does not know you made an assumption about their implementation.
The root of this problem is that whatever is depending on the producer needs to either:
- Requires the data as a dependency.
- Handle the state where the data is not available.
In the event the component requires the event data as a dependency, it should make it just that – a dependency. A required constructor dependency (or something injected via dependency injection) is guaranteed to be there 100% of the time (yes, you can argue this).
If it’s something that is not required, the component needs to handle the case when it’s not available (for a UI component, this usually means handling a loading state).
No library or tool will fix these problems for free, but some tools and patterns will encourage you to do things the right way.
An event bus, when used correctly, can probably avoid these issues. However, it also encourages these practices more than most tools. Using simple listeners, although they will require more code, will make things much easier to understand.
Update 6/14/15: I have changed the title (originally “Event bus is an anti-pattern”) to something a little less polarizing. It’s also worth reiterating that all of these pitfalls are not directly caused by the event bus. The synchronous getter issue in particular can happen with any kind of approach, but I think something like RxJava combined with optionals or
@Nullable can make it much more clear.