I guess every Android developer had at some point bumped into a callback hell, where you have to propagate some callback interface through thousands of objects in order to be able to do something when a user clicks on some UI element. And it gets even worse when you for example make it work across configuration changes such as orientation change. I had a similar pain a couple of weeks ago and had to come up with some solution to do the job without polluting all my UI components with huge amount of boilerplate.
To give you an idea of the scale of the problem, I will start with an example architecture of a typical UI. Imagine you have an Activity, which has a ViewPager containing several ListFragments generated by a ViewPagerAdapter. For each of these ListsFragments, there is a ListAdapter which builds the necessary views contained in the list. Everything is clean and looks sweet and you are feeling proud of yourself. Now, imagine in some of these views there is a Button which has to somehow communicate up to the Activity level something… Now if you do it directly with some callback interface, you will have to propagate it from the Activity through the ViewPagerAdapter -> ListFragment -> ListAdapter -> View, which is just crazy. Such situations are really rare, and actually if you encounter them too often, then maybe you should take a second look at your design and how your UI is being built.
A simple callback was definitely not a solution in my case as well, as I could see it destroying my clean code and turning it into a monster which nobody would be able to understand. So I looked at alternative ways of enabling UI components to talk to each other. Thankfully, the Android framework offers enough ways to communicate between different application components. There are also third party libraries which implement additional solutions such as event buses. I found two such libraries - Otto and the Greenrobot EventBus - both of which I definitely recommend. However, they offered more than I really needed so I have decided to implement something simpler based on two Android components - Intents and BroadcastReceivers.
Solution with Intents and BroadcastReceivers
The solution presented in the following sections is based on the idea of broadcasting Intents for specific actions containing a payload carried in the Intents’ extras. So when one component needs to tell something to another, it just fires a broadcast Intent for an Action which the other component has registered BroadcastReceiver for. Now, I didn’t want to tell the whole system that a button was clicked in one of the lists in my application because of security, privacy and performance reasons. Therefore, I have decided to use a nice component from the support library called LocalBroadcastManager. According to the official documentation it provides the following nice features:
You know that the data you are broadcasting won’t leave your app, so don’t need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don’t need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system.
Designing the LocalMessenger
Although the LocalBroadcastManager has a really nice API and one could just create one BroadcastReceiver and call a couple of methods to make the whole thing work, I have decided to encapsulate all of it into a single class. I had a couple of goals when I was designing it:
Has to be accessible from everywhere and have a consistent state
Has to provide simple methods for broadcasting some payload for a specific Action
Has to encapsulate all BroadcastReceiver specific stuff and require listeners who register to implement just a simple interface
The first one is easy, I have decided to implement the LocalMessenger using the Singleton creational pattern. The LocalBroadcastManager itself is also a Singleton, so this pattern works perfectly for the wrapper as well.
The second one, comes almost out of the box thanks to the LocalBroadcastManager API. I have only encapsulated the Intent object creation so that clients of the LocalMessenger only need to define an action and a payload they want to send. The heavy lifting is done by the LocalBroadcastManager and these two methods for firing Broadcasts:
public boolean sendBroadcast (Intent intent) - This method fires a broadcast with the Intent and returns immediately. It returns false if there is no BroadcastReceiver registered for this Intent’s Action
public void sendBroadcastSync (Intent intent) - This method fires a broadcast with the Intent and blocks until its BroadcastReceiver is executed. If there is no receiver, then it returns immediately.
The third one is a little bit tricky. I have decided to use a simple listener interface with a single callback method containing just the message payload which app components need to implement in order to receive messages. This way they don’t have to build and manage their own BroadcastReceiver object instances or understand what Intents are. Instead, these instances are built and managed inside the LocalMessenger. The app components which want to listen for some message just need to register for a specific action and implement the provided interface.
Although this approach is clean, it introduces a possible memory leak. It is always a bad idea to keep hard references to stuff inside a singleton which has longer life than for example an Activity - think about orientation changes. So just to make the LocalMessenger extra safe, I have decided to use WeakReference to deal with this problem. In addition, WeakReferences help managing the encapsulated BroadcastReceivers better, i.e. when a registered listener object has been garbage collected, we can also internally unregister and free the corresponding BroadcastReceiver object encapsulated inside the LocalMessenger.