Using Custom Events in Android Apps

There are two major concepts that provide a foundation for building scalable, native Android applications: loosely coupling requests and responses via events, and moving CPU intensive operations off the main thread. This post looks at events. It seems the online documentation on putting together all the pieces of  using custom events is sparse, at best. I’ll show you one example of how to put all the pieces together to create, dispatch and listen for custom events.  The end result is you will be able to decouple your application and make them more flexible and much less likely to break.

I’ve talked about event-based architectures before, and I think they are even more important when building applications for devices. They give you the ability to take into account processor delays and inconsistent internet connections. Between the three pieces of working with custom events described below you should be able to get up and running pretty quickly. I don’t go into much detail on what’s inside the various example as you can find those individual details by searching for them. It’s putting it all together that’s typically the hardest part.

The Event Class. Here’s an example of the content of an Event Class. I’ve taken this several steps further than most examples to include showing how to handle multiple Event types, which are accessed in this example as enums, and implementing multiple listeners. You can also extend your listeners with additional information of just about any type. In this example, I show using String’s for passing messages, but you could just as easily use custom Objects. This should give you a more realistic example of what goes into a commercial application.

	public class MapViewControllerEvent extends EventObject{

		private static final long serialVersionUID = 1L;

		public MapViewControllerEvent(Object source){
			super(source);
		}
	}

	public enum MapViewEvent{
		/**
		 * Connection to the internet has been lost.
		 */
		CONNECTION_LOST("Connection to the internet has been lost."),
		/**
		 * Connection to the internet has been restored.
		 */
		CONNECTION_RESTORED("Connection to the internet has been restored"),
		/**
		 * The LocationService has shutdown. Check logcat for errors.
		 */
		LOCATION_EXCEPTION("There was an unknown error related to the LocationService"),
		/**
		 * Indicates the LocationService is initialized and ready.
		 */
		LOCATION_INITIALIZED("The LocationService has been initialized. NOTE: it still may fail after a start() attempt."),

		private String description;
		private MapViewEvent(String description){
			this.description = description;
		}
	}

	public interface MapViewControllerEventListener extends EventListener{
		/**
		 * Indicates there has been a location change received from LocationService.
		 * @param event
		 * @param message
		 */
		public void onLocationChangeEvent(MapViewControllerEvent event,String message);
		/**
		 * Indicates whether or not device has internet connectivity.
		 * @param event
		 * @param message
		 */
		public void onConnectionChangeEvent(MapViewControllerEvent event,String message);
	}

	protected EventListenerList eventListenerList = new EventListenerList();

	/**
	 * Adds the eventListenerList for MapViewController
	 * @param listener
	 */
	public void addEventListener(MapViewControllerEventListener listener){
		eventListenerList.add(MapViewControllerEventListener.class, listener);
	}

	/**
	 * Removes the eventListenerList for MapViewController
	 * @param listener
	 */
	public void removeEventListener(MapViewControllerEventListener listener){
		eventListenerList.remove(MapViewControllerEventListener.class, listener);
	}

	/**
	 * Dispatches CONNECTION and LOCATION events
	 * @param event
	 * @param message
	 */
	public void dispatchEvent(MapViewControllerEvent event,String message){
		Object[] listeners = eventListenerList.getListenerList();
		Object eventObj = event.getSource();
		String eventName = eventObj.toString();
		for(int i=0; i<listeners.length;i+=2){
			if(listeners[i] == MapViewControllerEventListener.class){
				if(eventName.contains("CONNECTION"))
				{
					((MapViewControllerEventListener) listeners[i+1]).onConnectionChangeEvent(event, message);
				}
				if(eventName.contains("LOCATION")){
					((MapViewControllerEventListener) listeners[i+1]).onLocationChangeEvent(event, message);
				}
			}
		}
	}

Setup Listeners. Here’s how to set up the listener in your Activity. As an example, I called my Event Class MapViewController, but you can name it anything you like:

_mapViewController = new MapViewController(this);
_mapViewController.addEventListener(new MapViewControllerEventListener() {

	@Override
	public void onLocationChangeEvent(MapViewControllerEvent event,
			String message) {
		String eventName = event.getSource().toString();
		if(eventName.contains("EXCEPTION")){
			//TODO let user know
		}
		else{
			//TODO push UX change
		}

	}

	@Override
	public void onConnectionChangeEvent(MapViewControllerEvent event,
			String message) {
		// TODO Auto-generated method stub

	}
});

Dispatch Event. And, here’s how to dispatch an Event:

dispatchEvent(new MapViewControllerEvent(MapViewEvent.LOCATION_INITIALIZED),"Attempting to start location service.");

EventListenerList Class. For some reason, which I would characterize as a major oversite, Android does not currently provide a public EventListenerList Class. This Class makes creating custom events so much easier. However, the folks of the Firefly Client project for Android saved the day and were kind enough to create and post one publicly under an Apache License. The code is a bit dated, and shows some minor warnings when building Android v4, but it will work just fine. You’ll need to include a copy of this Class in your project to make things work.

So, that’s pretty much it. I hope this info helps you build better and more succesful projects.

References:

Java Tutorial – General Information about Writing Event Listeners

Firefly Client Android – EventListenerList Source Code

[Edited: June 7, 2012 – fixed various minor typos]