
Should We Take The Bus?
In an Android app, an event bus can be a powerful ally, or it can be a terrible adversary.
An event bus is a mechanism for broadcasting a message from one object and delivering that message to many other objects that want to receive it. As a metaphor, if I were to personally scream a message, the air around me would be the event bus that delivers my message to your ears.
The purpose of an event bus is to decouple the sender of a message from any number of receivers of the message. The sender of the message doesn’t care if anyone is listening.
An event bus is a specific implementation of the more generalized concept of publish/subscribe (pub/sub) semantics.
Within Android development, the two most popular implementations of an event bus are EventBus (by greenrobot) and Otto (by Square).
The key to using an event bus in Android development is a crystal clear understanding of when it is appropriate to use the event bus, versus when it is inappropriate to use the event bus. Mixing up these situations can have a costly impact on your application architecture.
When To Use An Event Bus
Let’s jump right in with a real-world example where an event bus might be a good notification choice:
Imagine that you have a FitnessTracker object that needs to know when the user’s location has changed. There is also a system somewhere that figures out when the user’s location has changed, lets call it a LocationMonitor. When the LocationMonitor recognizes a change in the user’s location, the LocationMonitor posts a UserLocationChangedEvent to the event bus, and the event bus delivers the UserLocationChangedEvent to the FitnessTracker (and possibly others).
The fitness tracker example demonstrates the following event bus benefits:
- There could be many other systems that also care about location changes, but neither our FitnessTracker, nor our LocationMonitor care one bit about those other systems. Therefore, complexity of both systems is minimized.
- Using the event bus, the FitnessTracker doesn’t care one bit whether there is one LocationMonitor or multiple LocationMonitors feeding the data. Moreover, the FitnessTracker doesn’t know the difference between a real LocationMonitor and a MockLocationMonitor that might be used for testing. Thus, we have established a strong encapsulation boundary around location monitoring which frees up the FitnessTracker to focus on its single responsibility.
- When the LocationMonitor starts broadcasting, it doesn’t care if anybody is listening. When the FitnessTracker starts listening, it doesn’t care if anyone is broadcasting. Thus, the event bus allows the lifespan of the LocationMonitor and the FitnessTracker to vary independently without any repercussions.
- The FitnessTracker does not need to know where to find a LocationMonitor.
What can we surmise about when to use an event bus? Consider using an event bus when:
- One system needs to send a message to (possibly) multiple other systems
- Listeners don’t care about who sent the message
- Broadcasters and listeners do not otherwise require any direct references to one another
- Broadcasters do not require anyone to be listening, and listeners do not require anyone to be broadcasting
If you leverage an event bus based on the above guidelines, the event bus will give you great decoupling power.
When NOT To Use An Event Bus
The power of the event bus has a way of turning developers to the dark side. If a little decoupling is good then infinite decoupling is better, right?
Wrong!
Some objects really should know about others. A CreditCardProcessor should know about CreditCards. An AppointmentScheduler should know about Calendars. A CoffeeBrewer should know about a Percolator and a HeatPlate. Sticking an event bus between these objects won’t help decouple your objects, but it will result in code that no one can follow or reason about.
Do not use an event bus when what you need to do is make a request.
What does it mean to make a request? If ObjectA needs ObjectB to do something, then that is a request (or dependency) relationship. A CoffeeBrewer needs a Percolator to do its job — there is no getting around that fact. Do not use an event bus to represent necessary and/or fundamental relationships between objects.
This is an example of what not to do:
// In the CoffeeBrewer class.
public void brewCoffee() {
// ...
eventBus.post(new StartPercolateRequest());
// ...
}
// In the Percolator class.
public void onEvent(StartPercolateRequest request) {
// Do percolation
}
Instead, make direct dependencies explicit:
// In the CoffeeBrewer class.
public void brewCoffee() {
// ...
percolator.percolate();
// ...
}
In terms of the guidelines mentioned earlier, consider how the CoffeeBrewer/Percolator violates those guidelines. Does the CoffeeBrewer care if anyone is listening for the StartPercolatorRequest? Absolutely. The CoffeeBrewer has a big problem if nobody starts percolating. Are the lifecycles of the CoffeeBrewer and Percolator independent? No. The CoffeeBrewer requires the presence of a Percolator for the entirety of its existence — there is a true dependency from CoffeeBrewer to Percolator. Would it ever be appropriate for multiple Percolators to receive the StartPercolatorRequest? Doubtful. Its more likely that multiple receivers would lead to a major mess on your counter.
The fundamental distinction to notice in the CoffeeBrewer example, is that the CoffeeBrewer is not broadcasting update information — the CoffeeBrewer is making a request. The CoffeeBrewer still has a job to do and it requires another component to handle part of that job. There must be an object that is online and ready to handle the needs of the CoffeeBrewer. And that object needs to specifically be a Percolator. There is nothing to decouple here, there is no boundary to maintain, there is no gain from separating these objects. The only possible impact that an event bus will have on this situation is to break the CoffeeBrewer in a way that you won’t discover until runtime.
When using contrived examples this issue might sound trivial. Who would stick an event bus between a CoffeeBrewer and a Percolator? Of course real world projects are not always so obvious and I have seen this exact problem present itself in multiple Android projects. It quickly becomes impossible to comprehend the flow of control, and good luck debugging anything.
So I will say again, do not use an event bus to implement request (dependency) behavior.
Naming Event Bus Events
I’ve noticed naming schemes for events that I believe obfuscate their meaning and should be avoided.
Sometimes developers broadcast data instead of events and this data has an implied context that lead to its dispatch. Consider the following notification options:
Location location = // some location data
bus.post(new UserLocationChangedEvent(location));
versus
Location location = // some location data
bus.post(location);
Both of these bus posts will get the necessary information to the objects that are listening, but the first option makes the context of the broadcast explicit — the user’s location has changed. The second example does not provide any explicit indication about what the location means. Is it the user’s location, or a special location on a map, or a friend’s location? We don’t know. This information has been hidden from future developers.
I recommend creating objects for the sole purpose of representing the context around the broadcast. These objects may be nothing but a wrapper around a single piece of data, but that’s OK because their job isn’t to do anything special, their job is to communicate to future developers why they have been broadcast.
Why Not Use BroadcastReceivers Instead?
Android provides broadcast mechanisms that can broadcast Intents to any BroadcastReceivers registered for the given Intent. So why not just use BroadcastReceivers?
BroadcastReceivers are designed to be used across Android components and therefore they require excessive plumbing to operate. Any information you want to send with the broadcast needs to be serialized within an Intent, and then deserialized by the BroadcastReceiver. Also BroadcastReceivers are tied to a Context in one way or another which means any objects using this broadcast system are sitting squarely in Android framework territory which isn’t good for code portability or unit testing.
Generally speaking, Android’s broadcast system is heavy — it requires a lot of stuff that you usually don’t want to deal with, and offers abilities that you don’t usually care about. Use BroadcastReceivers if you need what they offer, otherwise go with a lighter, Java-only solution.
EventBus vs Otto
The two most popular event bus implementations for Android apps are EventBus and Otto.
I started my event bus experience with Otto and then later moved to EventBus. In my experience there is only one quality that differs between the two buses that ever mattered to me. With EventBus, if a superclass registers with the event bus and declares event handlers then subclasses will also receive those events. With Otto if you subclass a class that registers with the event bus then you must re-implement the event handlers in the subclass. Therefore, I decided to go with EventBus because it seemed natural to me that a subclass should inherit all behaviors of its superclass including any event-oriented behavior.
Summary
Do use an event bus when a well encapsulated system wants to broadcast an update to zero or more listening systems.
Do not use an event bus when one system needs to make a request of another system — this is a dependency relationship and should be made explicit.
Do create and name broadcast events based on the context of their broadcast — the name should answer the question “Why was this event broadcast?”
Do prefer non-Android broadcast systems over Android broadcast systems. This preference will allow you to write unit tests and allow you to maintain boundaries between your business logic and your Android logic.