Using an EventBus in Android Pt 1: Why an EventBus?

Originally published January 22, 2015

An EventBus is a great tool for decoupling components in your application. Over the next few posts I will describe the ways that I have been using it to make my code cleaner, easier to read, and easier to test. But first, this week I want to discuss why I use an EventBus in the first place. In particular, I will compare its use to some alternative techniques.

vs. Listener Interfaces

A common pattern in Java, and particularly in Android is the use of Interfaces to define “Listeners”. In this pattern, the class implementing the Listener Interface must register itself with the class to which it will listen. This means that the listener component has a hard dependency on the listenee component. Such tight coupling makes your class difficult to unit test. Jake Wharton describes this problem well in his post Decoupling Android App Communication with Otto), so I will just leave it at that.

vs. LocalBroadcastMessager

Another technique that is used is to make use of the LocalBroadcastMessager to send and listen for messages accross components. While this works well for decoupling your code, it’s API is not as clean as that of an EventBus. Also, it introduces potential run-time/type errors if you are not careful about keeping your Intent extras in sync.

Consider the following scenario:

  • Component A performs an asynchronous batch update of a records in a database. It if completes successfully, it sends a notification with the number of records updated. If it encounters an error, it sends a notification with the error message.
  • Component B needs to be informed of both when the update is complete, as well as how many records were updated. It also needs to be informed if there is an error, with the error message.

Using the LocalBroadcastManager, the minimum amount of code required would look something like follows.

Component A:

// when update completes successfully
Intent intent = new Intent();
intent.setAction(FILTER_ACTION_UPDATE_SUCCESS);
intent.putExtra(EXTRA_COUNT, count);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);

...

// when there is an error
Intent intent = new Intent();
intent.setAction(FILTER_ACTION_UPDATE_ERROR);
intent.putExtra(EXTRA_ERROR_MSG, error.getMessage());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);

Component B:

IntentFilter updateSuccessFilter = new IntentFilter(FILTER_ACTION_UPDATE_SUCCESS);  
BroadcastReceiver updateSuccessReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int updateCount = intent.getIntExtra(EXTRA_COUNT, -1);
// TODO - do something with the count
}
};

IntentFilter updateErrorFilter = new IntentFilter(FILTER_ACTION_UPDATE_ERROR);
BroadcastReceiver updateErrorReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String errorMsg = intent.getStringExtra(EXTRA_ERROR_MSG);
// TODO - display error message or similar
}
};

...

getContext().registerReceiver(updateSuccessReceiver, updateSuccessFilter);
getContext().registerReceiver(updateErrorReceiver, updateErrorFilter);

...

getContext().unregisterReceiver(updateSuccessReceiver);
getContext().unregisterReceiver(updateErrorReceiver);

Using an EventBus, the same functionality can be expressed as follows. (Note: for this example I am using my EventBus implementation of choice, Green Robot’s EventBus.)

Event Classes:

public class UpdateCompleteEvent {  
public final int count;

UpdateCompleteEvent(int count){
this.count = count;
}
}
public class UpdateErrorEvent {  
public final String message;

UpdateCompleteEvent(String message){
this.message = message;
}
}

Component A:

EventBus bus = EventBus.getDefault(); // Or better yet, inject it

...

// when update completes successfully
bus.post(new UpdateCompleteEvent(count));
...

// when there is an error
bus.post(new UpdateErrorEvent(error.getMessage()));

Component B:

EventBus bus = EventBus.getDefault(); // Or better yet, inject it

...

public void onEvent(UpdateCompleteEvent event){
// TODO - do something with event.count
}

public void onEvent(UpdateErrorEvent event){
// TODO - do something with event.message
}

...

bus.register(this);

...

bus.unregister(this);

As you can see, using the EventBus not only keeps the code cleaner, but it is also more type-safe. When using an Intent to pass data, there is no compile-time check that the “extra” you set is the same type as what you receive. So it’s easy for you or another developer on your team to change the payload of an Intent without appropriately updating all of its receivers. Doing so won’t result in any compilation errors, so you will only find out there is a problem at run-time.

Using an EventBus your message payload is carried via Event classes that you define. Since your receiver methods deal with instances of these classes directly, all of the payload properties are type-checked, and any mismatched handling results in an error at compile time.

What’s more is that your Event classes can be anything at all. While I typically create specifically named classes explicitly for the purpose of representing events, you could just as well post/receive any other class to the bus. In this way, you are not bound to the limits of simple datatypes that may be added as Intent extras. For example, you might want to post an instance of an ORM model class, and have your receiving class perform ORM-specific operations directly on the received instance.

Hopefully I have made a strong case for using an EventBus in your Android applications. In my next post I will discuss how I have been making use of Green Robot’s EventBus “sticky” events.