By now, you should be familiar with the various ways the Android framework relies on passing data between the various abstractions used:
One of primary issues of these current approaches is that they can create strong dependencies between each component, making it difficult to change one part of the system without impacting another area. This blog post describes the challenges of creating unmanageable dependencies with the current Android framework. Instead of encouraging more modular designs, the communication patterns can sometimes do the exact opposite.
Publish/subscribe models try to avoid this tight integration by relying on an event bus model. In this type of model, there are publishers and subscribers. Publishers are responsible for posting events in response to some type of state change, while subscribers respond to these events. The event acts as an intermediary for exchanging information, isolating and minimizing the dependencies between each side. In this way, message buses create a communication pipeline that is intended to help make your app more maintainable and scalable.
One additional benefit of using these pub/sub frameworks is that they help facilitate passing Java objects between Activities, Fragments, or Services. You don't need to spend time serializing and deserializing data, which can often creates the tight bindings between these components. It also helps enforce more type-safety across complex Java objects.
There are many different libraries which attempt to enable the event bus model, including EventBus, RxJava, and Otto. Otto has been deprecated in favor of RxJava/RxAndroid approaches. EventBus has a few more advanced features than in Otto described in this comparison chart and recently has become the more supported Java library.
Event buses are especially helpful include notifying activities or fragments when tasks are completed, such as when a AsyncTask or a Background Service finishes. When using an event bus model, there are several considerations:
Don't assume you should replace every communication pattern with an event bus model. If you publish an event meant only for one subscriber that shouldn't trigger changes in other subscribers, rely on 1-to-1 communication patterns such as the Intents or listener pattern. For instance, a date picker fragment that could be reused in multiple components of your app should probably expose a listener interface since using a event bus model could cause this event to be published to many components waiting to respond to changes in the date selection. Consider the design patterns described in this article too.
If you are using publishing or subscribers within an Activity of Fragment they should be registered and unregistered with their lifecycle. Otherwise, it's likely you will encounter memory leaks or dangling references that can cause your app to crash.
Be wary about publishing events between Fragments. Events cannot be published or received when a Fragment is not running. If you have a Fragment publishing a message to another Fragment that isn't currently executing and then swap one for the other, it's likely the event will not be processed correctly. The EventBus library has a way to replay this event, but the Otto framework does not.
Add this Gradle dependency to your app/build.gradle
file:
dependencies {
implementation 'org.greenrobot:eventbus:3.0.0'
}
One primary use case that the event bus model works well is replacing it lieu of the LocalBroadcastManager when communicating from a service to the application. One advantage is that you can simplify a lot of code that normally is used to serialize or deserialize the data by avoiding the need to pass Intents.
If you wish to subscribe Activities and Fragments to the event bus, it should be done with respect to the lifecycle of these objects. The reason is that when an event is published, EventBus will try to find all the registered subscribers. If there is a subscriber attached to the Activity currently not running, there is likely to be a stale reference that causes your app to crash.
public class MainActivity extends AppCompatActivity {
@Override
protected void onPause() {
super.onPause();
EventBus.getDefault().unregister(this);
}
@Override
protected void onResume() {
super.onResume();
EventBus.getDefault().register(this);
}
Create a new Event from a standard Java class. You can define any set of members variables. For now, we create a class to accept a result code and a String values.
public class IntentServiceResult {
int mResult;
String mResultValue;
IntentServiceResult(int resultCode, String resultValue) {
mResult = resultCode;
mResultValue = resultValue;
}
public int getResult() {
return mResult;
}
public String getResultValue() {
return mResultValue;
}
}
Inside our Intent service, we can should publish the event. We will simply pass an OK
result with
a message that will be displayed when the event is received on the Activity.
@Override
protected void onHandleIntent(Intent intent) {
// do some work
EventBus.getDefault().post(new IntentServiceResult(Activity.RESULT_OK, "done!!"));
}
We simply need to annotate a method that has this specific event type with the @Subscribe
decorator. The method must also take in the parameter of the event for which it is listening. In this case, it's IntentServiceResult
.
Because the IntentService
is executed on a separate thread, the subscribers will normally also execute on the same thread. If we need to make UI changes on the main thread, we need to be explicit that the actions executed should be done on the main thread. See this section for more information.
public class MainActivity extends AppCompatActivity {
@Subscribe(threadMode = ThreadMode.MAIN)
public void doThis(IntentServiceResult intentServiceResult) {
Toast.makeText(this, intentServiceResult.getResultValue(), Toast.LENGTH_SHORT).show();
}