Event bus is a software component that can be used to exchange messages between different parts of the system. In other words, event bus is a centralized software communication hub.
As you’ll see shortly, implementing basic event bus is simple. However, despite its apparent simplicity, this component is a tricky beast to tame. In some sense, you can think of an event bus as a lightsaber: properly trained, disciplined Jedi can use it to fight enemies and deflect blaster beams, but an average Joe would probably cut himself in two if he’d try to wield this powerful weapon.
Therefore, in this post, I’ll summarize my experience with event buses to help you adopt this approach in your projects while avoiding the most common pitfalls.
Simple Event Bus Implementation
You can implement a very simple event bus like this:
public class EventBus { public interface Event {} // "marker" interface public interface Subscriber { public void onEvent(Event event); } private final Set<Subscriber> mSubscribers = new HashSet<>(); public void publish(Event event) { for (Subscriber subscriber : mSubscribers) { subscriber.onEvent(event); } } public void subscribe(Subscriber subscriber) { mSubscribers.add(subscriber); } public void unsubscribe(Subscriber subscriber) { mSubscribers.remove(subscriber); } }
The above is the simplest implementation of an event bus which wouldn’t be practical to use in many situations (e.g. it’s not thread-safe), but it does capture the main idea.
At a higher level of abstraction, event buses realize so-called Publish-Subscribe architectural pattern (“pub-sub”). In this pattern, there is one group of clients that produce and publish notifications (events), and another group of clients that subscribe to receive these notifications. The main feature of pub-sub is that publishers and subscribers don’t need to know anything about each other, as long as they “agree” on the “type” of notifications being exchanged. Alternatively, we can say that pub-sub results in lack of direct coupling between publishers and subscribers, which lowers the overall coupling in the system.
Low coupling is great, we all know that, right? Well, turns out that there is more nuance here than meets the eye initially.
Defusing the Low Coupling Argument
In my experience, “low coupling” is universally seen as a positive characteristic. But is this always the case?
Theoretically, you could route all the communication between components in your system through an event bus. Just replace every inter-component method call with a specific event and you’re done. The end result of this experiment will be a system where components won’t have any direct inter-dependencies. Software architect’s paradise, isn’t it? [A similar approach was implemented, at scale, in multiple companies, as part of their “decoupled micro-services” initiatives.]
I hope that even though I invoked the magical “low coupling” argument, your intuition screams that something isn’t right about this idea. However, it’s not that simple to point out what’s the problem here exactly. That’s why “low coupling” idea often becomes a trap.
Let’s defuse this trap.
For one, it’s not universally true that event buses lead to lower coupling. For example, let’s say that ComponentA
called a method on ComponentB
and you replace this method call with an event. In this scenario, you effectively replace ComponentA
‘s dependency on ComponentB
with dependency on the event bus itself. ComponentB
will also need to subscribe to the event bus, so that’s one more dependency to add. Lastly, both ComponentA
and ComponentB
will need to depend on a specific type of event that will be used for communication, so this also couples them together. Therefore, the overall coupling inside the system can actually increase as a result of event bus introduction.
Even if you look at ComponentA
exclusively, the situation isn’t necessarily better with event bus. Before the refactoring, ComponentA
was coupled to a specific method in ComponentB
. If you’d remove that method, your code would either fail to compile (statically typed language), or fail at runtime (dynamically typed language). In either case, you’d get a clear error and would be able to debug the problem. However, after refactoring to pub-sub, you could remove the logic which handles the events from ComponentB
(or just forget to add it in the first place, or forget to subscribe ComponentB
to the event bus), and that specific functionality would just fail silently. No crashes, no errors, just frustrated users. This can be mitigated to some degree with automated testing, but even then it’s not that trivial. This potential problem is a direct symptom of low coupling: the coupling between ComponentA
and ComponentB
isn’t strong enough to provide explicit integration indicators. Therefore, in this case, insufficient coupling becomes a drawback, not a benefit.
So, is low coupling bad then? Not at all. It’s only when the coupling becomes lower than the optimal that it becomes a problem. And that’s really the main idea that I wanted to convey: software components need to be inter-coupled to the optimal degree, or, at least, close to that.
In some cases, the required level of inter-coupling between components should be very low. PCIe protocol comes to mind as an example. It’s also a pub-sub, but much more complex than event buses. Hardware components which connect to PCIe aren’t coupled to other components in the system, which allows you to use a vast variety of parts from different manufacturers inside your PC.
In other cases, like if you have two inter-dependent classes inside your codebase which implement a single feature, you want to have explicit direct coupling. Future maintainers of your codebase don’t need to trace events and reverse-engineer lifecycle overlaps to figure out communication scheme between two classes which reside in the same package. A simple method call is much better choice in this situation.
Event Bus Best Practices
To make an efficient use of an event bus in your projects, it would be great to have a set of specific practical guidelines. These are my personal best practices when working with event buses:
- Default to not using an event bus for communication
- Consider using event bus when it’s difficult to couple the communicating components directly
- Avoid having components which are both publishers and subscribers
- Avoid “event chains” (i.e. flows that involve multiple sequential events)
- Write tests to compensate for insufficient coupling and enforce inter-components integration
- Put event classes into the respective “feature” packages
Event Bus Interface Segregation
To support the third best practice from the above list (“avoid having components which are both publishers and subscribers”), I like segregating between publishers and subscribers explicitly. To achieve this goal in statically typed languages, instead of using event buses directly, you can wrap them into “proxies”:
public class EventBusSubscriber { private final EventBus mEventBus; public EventBusSubscriber(EventBus eventBus) { mEventBus = eventBus; } public void subscribe(EventBus.Subscriber subscriber) { mEventBus.subscribe(subscriber); } public void unsubscribe(EventBus.Subscriber subscriber) { mEventBus.unsubscribe(subscriber); } } public class EventBusPublisher { private final EventBus mEventBus; public EventBusPublisher(EventBus eventBus) { mEventBus = eventBus; } public void publish(EventBus.Event event) { mEventBus.publish(event); } }
The benefit of using these proxies is that you’ll be able to infer whether a component is a publisher or a subscriber just from its signature. That’s very beneficial for long-term maintainability. Developers who took my SOLID course will surely recognize this design trick as an example of applying Interface Segregation Principle.
Event Bus in Android
The discussion up until now was very general and applies to pretty much all programming languages and frameworks. However, I also want to share some additional recommendations specifically for Android developers.
First of all, in Android projects, I recommend using EventBus library. I know that “you can implement it in RxJava in just couple of lines of code”, but, as much as I like DIY stuff, I don’t think it’s a good idea.
If you do use this library, then also consider these best practices:
- Use ThreadMode.MAIN_ORDERED in all subscribers by default.
- Don’t ever use EventBus as a multithreading framework (i.e. for offloading work to background threads).
- Sticky events can be very handy in some very specific situations, but realize that once you use them, you basically store part of app’s state inside the event bus. In most cases, this trade-off isn’t justified.
- You shouldn’t need EventBus’s annotation processor in Android apps. If you ever suspect that you do need it, treat it as a code smell and re-evaluate your design approach.
After you get your hands on this cool library, you’ll most probably see dozens of places where it can make your life much simpler. Many of these will be non-optimal and will lead to maintainability issues down the road. So, be careful.
Stuff I routinely use EventBus for:
- Sending user interaction notifications from DialogFragments to their “host” Activities and Fragments.
- Sending notifications from Services to Activities and Fragments.
- Non-domain communication between multiple on-screen controllers (e.g. synchronizing non-domain UI state between multiple Fragments) .
The common denominator here is that it’s relatively cumbersome to implement direct coupling between these components due to the architecture of Android framework itself. Therefore, any alternative solution will also be “dirty” to some degree. If that’s the case, then just using EventBus becomes a reasonable trade-off.
Conclusion
Event buses have been integral part of my toolbox for years and I use them in pretty much all projects. However, when I just found out about this seemingly simple and powerful approach, I abused it enormously. It took me years to realize the long-term negative effects of insufficient coupling and become respectful and mindful of event buses. I hope you won’t need to go through a similar trouble after reading this post.
As usual, thanks for reading. You can leave your comments and questions below.
Would you use event bus for activity to service communication as well?
In theory, you can do that. However, you’ll need use some other means to ensure that the Service is “alive” because just subscribing to event bus won’t guarantee that.
Thank you for the sharing your insights 🙏. I wonder what do you think about using the Fragment result API https://developer.android.com/guide/fragments/communicate#fragment-result
to communicate user interaction instead of the Event Bus?
I suppose it works the best when you need system to re-emit event between process deaths. Though, this might be unnecessary here. What is your take on this?
Hey,
I discussed Fragment Result API in this article.
Hi,
Your article mentions:
1. onActivityResult based navigation
2. Communication between Fragment with setTargetFragment/getTargetFragment
3. Screens Data Return Buffer as a Navigator abstraction
I did not found a discussion of setFragmentResult/setFragmentResultListener API usage. Am I missing smth?
Ah, sorry, I thought about setTargetFragment/getTargetFragment.
I didn’t know about FragmentResult API, so thanks for bringing it to my attention. From the docs, this scheme looks like a decent approach for communicating results between Fragments. It’s basically a map of Bundles inside FragmentManager that is automatically persisted on config changes and process death. The fact that this scheme takes care of the process death is a big benefit over “my” approach. I’ll experiment with this newer API when I get a chance.
Yes it makes sense. In general I always struggle on how to interact property with services in more architectural way. Should I somehow interact from viewmodel (or presenter in your case). Or from activity/fragment. I think that there are way too many code samples on how to load data from rest or db, with MVx, very few on interaction with service holding audio player or MQTT, web socket, bluetooth low energy connection or other more platform specific code. Hopefuly an idea for your next long post? ?
It might be, though I’ll need to do a research because I’ve been out of the loop with Services for quite some time. I’m subscribed to this issue in Google’s issue tracker and keeping a Service alive sounds like a proper nightmare.
These days I mostly use bound services as something that will be alive while any activity of the app is in started state for foreground live communication that comes from socket connection for example but it is always mess to communicate with services.
Thank you for all of the responses. Best regards.
Thank you for the post, Vasiliy. As I understand you consider event bus as the replacement for broadcast receivers?
Dmitriy,
You most probably mean LocalBroadcastReceiver. In this case, yes, event bus is much better alternative. As for general BroadcastReceiver, event buses can’t fully replace them becuase the former supports inter-process communication.