Communication patterns for application components

Activities, services, fragments, helper classes etc. are main components of Android applications but its tricky to establish communication between these components. It’s tricky when one cares about writing reusable code – loosely coupled, plug-n-play-able. The goal here is to avoid tight coupling.

Tight coupling – Components keep references of each other and call methods on them directly. In the code below, we are keeping a reference of MagazineActivity inside MenuFragment. So, MenuFragment is tightly coupled with MagazineActivity i.e., it cannot function without MagazineActivity.

// TIGHT-COUPLING EXAMPLE

class MenuFragment extends Fragment {
    private void onArticleClick(int articleId) {
	MagazineActivity magAct = (MagazineActivity) getActivity();
	magAct.showArticle(articleId);
    }
}

This leads to lot of dependency among components. In such a design, making a change in a class can cascade down to a number of collaborating classes and in vast applications, managing this can be a pain. Further, if the development process involves multiple developers, the application would get terribly error-prone.

On the other hand, Loose coupling is a design goal that seeks to reduce the interdependencies between components. A loosely coupled system can be easily broken down into definable elements. This makes the system more flexible and maintainable. In a multiple developers scene, each developer can handle an independent module that would interact with other modules over standard protocols.

Conventional Approach to loose coupling: Interfaces

Interface is a powerful tool to use for decoupling. Classes can communicate through interfaces rather than talking directly. A class can provide an interface for other classes to communicate with it. This also provides an abstraction layer as other classes don’t need to bother about implementation. This is the same as you interacting with an ATM machine. As long as you can make sense of the keypad, you are good to do your transaction on any ATM machine without bothering about its internal implementation details.

// INTERFACE EXAMPLE

class MenuFragment extends Fragment {
    public static MenuFragment instantiate(ArticleSwitcher articleSwitcher) {
	MenuFragment menuFragment = new MenuFragment();
	menuFragment.articleSwitcher = articleSwitcher;
	return menuFragment;
    }

    ArticleSwitcher articleSwitcher;
	
    public void onArticleClick(int articleId) {
	articleSwitcher.showArticle(articleId);
    }
}

There are following downsides to use of interface:

  1. Components need to know about each other in order to pass interfaces. So a bit of dependency still remains.
  2. Interfaces cannot be passed via intents. So at times, android specific components would not be able to talk through interfaces.
  3. In a vast application with inter-linked components, the number of interfaces might grow excessively, leading to hell lot of boiler-plate code and increased complexity, especially when an interface requires propagation through multiple components. For example, a helper class inside an Activity needs to pass a message to a Fragment. This would lead to interface chaining.

 

Elegant solution: Message Bus

Communication based on publish-subscribe pattern. A publisher broadcasts an event and subscribers get notified of it and can act upon it. Thus, the subscribers and publishers are purely decoupled.

This is similar to NSNotification and NSNotificationCenter in iOS.

 

Implementations of MessageBus

1. Implicit-Intent and BroadcastReceiver MessageBus

Communication based on implicit intents can be considered as an implementation of message bus. An intent is fired with an action using sendBroadcast() or startActivity() and registered BroadcastReceivers or Activities with corresponding IntentFilter will get notified. This implementation due to being Intent based has following upside and downside:

Upside: Can connect components of different applications. For instance, your application can publish an implicit intent to display an image and you see a Chooser of all those Activities that are capable of displaying image.

Downside: Data in Intent is stored in a bundle and bundles cannot carry complex data, leading to the overhead of implementing Serializable or Parcelable interfaces.

FluffyEvents is a MessageBus library based on communication through BroadcastReceivers.

 

2. EventBus: Event based MessageBus

Various events can be published and subscribed for. A subscriber component would get notified of an event whenever a publishing component posts that type of event on the bus. An event can be any java class, such as following..

// SAMPLE EVENT CLASS

class DownloadProgressEvent {
    private float progress;
    public DownloadProgressEvent(float progress) {
	this.progress = progress;
    }

    public float getProgress() {
	return progress;
    }
}

EventBus: Behind the scene:

EventBus keeps track of events and subscriber methods of active classes in map data-structures, such as following used by Otto.

/** Cache event bus subscriber methods for each class. */
private static final Map<Class<?>, Map<EventClass, Set>> SUBSCRIBERS_CACHE = new HashMap<Class<?>, Map<EventClass, Set>>();

This mapping is mostly done in following 2 steps:
1. Loop over methods of a class using reflection.
2. If it’s an annotation-based EventBus, loop over annotations and fill the map. In convention based EventBus, use the convention to fill the map.

Note: Annotation processing is quite an intensive task in java. So, annotation-based EventBus might introduce some lag.

This map is refreshed each time a class gets registered or unregistered on the Bus. Typically, an Activity or Fragment should be registered in onResume and unregistered in onPause. Helper classes should be registered/unregistered from the owning class i.e. Activity, Fragment or Service. Once an event is posted on the Bus, the above Map is looped and methods mapped to this Event are called.

Sticky Event
Some events carry information that is of interest after the event is posted. 
For example, 'last known location'. 
A subscriber gets notified of such events during registration and these events can be queried anytime.

As this concept of event is not any standard in Android, EventBus cannot be used for inter-app communication. Further, the Bus class will stay local to an application. Currently, Android doesn’t have any native implementation of Bus. Although, one can write his own Bus class, it’s better to use one of existing EventBus libraries. Top two of that list are Square’s Otto and GreenRobot’s EventBus.

Square’s Otto: This is Annotation based EventBus. Light-weight and easy to use. Based on Java EventBus implementation in Guava library, Otto has been optimised for Android. Provides threading options but very limited.

GreenRobot’s EventBus: This is convention based EventBus. Better performance than any other EventBus for Android. This mostly comes out of it not using Annotations for marking methods as querying annotations is a slow process. Much flexible threading options.

Conclusion:

Balance is always necessary. And in this discussion, the balanced point lies somewhere between the use of Interfaces and Bus (Thus involving both in an application). Interfaces lead to increase in boiler-plate code but are standard and efficient. EventBus makes the code purely decoupled and simple but the involved processing (reflection, annotation etc) takes quite a bit of toll on performance. Ideally one should stick to Interfaces if it doesn’t require chaining over multiple classes. EventBus should be used for communicating between classes that are not directly linked.

Leave a Reply

Your email address will not be published. Required fields are marked *