Android for ActionScript developers, part 2: Events and listeners

The first thing that made me pause when working with Java is discovering that it doesn’t have event listeners – at least not in the sense that I’ve come to expect based on my ActionScript experience.

In ActionScript 3, you attach an event to an object like so:

myObject.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);

protected function onMouseDown(e:Event): void {
    trace("The object " + e.target + " was clicked.");
}

Considering that the second parameter to the addEventListener method is a function reference – a closure – you can reuse the same function in as many instances of as many objects as you like. Furthermore, you can attach several listeners to the same event on the same instance; this means that several different objects can track the same event.

Creating custom events is also fairly easy. In way, you don’t need any real class; just a string. You can certainly do something like:

myObject.addEventListener("mysuperevent", onSuperEventTriggered);

And you can easily trigger them from your object:

dispatchEvent(new Event("mysuperevent"));

You can also create new custom events, if you wish, so the event dispatched can carry more data with it. This can be done with separate event classes, like so:

public class DestructionEvent extends Event {

	// Enums
	public static const EXPLODED:String = "exploded";	// Object has exploded
	public static const IMPLODED:String = "imploded";	// Object has imploded

	// Properties
	_customData = __customData;

	// ================================================================================================================
	// CONSTRUCTOR ----------------------------------------------------------------------------------------------------

	public function DestructionEvent(__type:String, __customData:Object, __bubbles:Boolean = false, __cancelable:Boolean = false) {
		super(__type, __bubbles, __cancelable);
		_customData = __customData;
	}

	override public function clone(): Event {
		return new NavigableSpriteEvent(type, bubbles, cancelable);
	}

	public function get customData(): Object {
		return _customData;
	}

They can be easily dispatched:

dispatchEvent(new DestructionEvent(DestructionEvent.EXPLODED, {id:myId, somedata:"Some data"}));

And easily caught:

myObject.addEventListener(DestructionEvent.EXPLODED, onExploded);

protected function onExploded(e:DestructionEvent): void {
    trace("The object " + e.target + " has exploded, and it was carrying the " + e.customData + " custom data with it.");
}

Despite some syntax awkwardness (especially when transitioning from the ActionScript 1 and ActionScript 2 models), some performance problems, and some great alternatives, this system does the job well, allowing developers to glue together encapsulated pieces of code pretty seamlessly, and is supported consistently through the AS3 ecosystem.

Hence my surprise when I got to Java.

First of all, Java doesn’t allow closures (at least not yet), so you can’t pass functions as parameters for anything. This means I couldn’t even build an event dispatching (or signals) system like the ones I was used to in ActionScript 3.

The first examples I found covering the event model on Java were pretty awful. They were something like this:

public class MyClass implements OnClickListener {

	public MyClass() {
		Button myButton = new Button();
		myButton.setOnClickListener(this);
	}

	public void onClick(Object o) {
		Log.v("", "Object " + o + " was clicked);
	}
}

The problem with this is obvious: by forcing the instance that contains an object to implement a listener interface to the object, it doesn’t allow me to track the same event from two objects at the same time (or even listeners that have method name collisions). In my above example, if I wanted to have two buttons, I would need an additional condition inside onClick to detect where the event came from. This could easily get ugly.

The reason why I’m mentioning it here is because that kind of example was quite easy to find and it baffled me for a while that someone would adopt that kind of design for complex applications.

As it turns out, however, event listeners in Java are a little bit better than what that typical example makes it look like. You can create and extend an object of a class (even an interface!) inline – so the real way to write the above code is something like this:

public class MyClass {

	public MyClass() {
		Button myButton = new Button();
		myButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(Object o) {
				Log.v("", "Object " + o + " was clicked);
			}
		});
	}
}

Still a little bit strange to me (still used to AS3 I suppose), but much easier to read and use.

Implementing custom events is also pretty easy. Here, again, I did a mistake initially – I thought you had to create a separate class for every kind of event you wanted. My initial classes were always awkwardly accompanied by a bunch of separate interfaces for events, making my own packages pretty polluted.

As it turns out, there’s a better, cleaner way of declaring events too: as interfaces declared inside the classes themselves, and then referenced from outside. This seems to be the norm in Java conventions. In a nutshell, my classes that require an event look like so (in a hypothetical button clas):

public class MyButton {

	// Instance references
	protected OnClickListener onClickListener;

	// Constructor
	public MyButton() {
		...
	}

	// Public methods
	public void setOnClickListener(OnClickListener __listener) {
		onClickListener = __listener;
	}

	// Interfaces
	public interface OnClickListener {
		public void onClick(MyButton __button);
	}

	// Internal dispatching
	protected void dispatchOnClick() {
		if (onClickListener != null) onClickListener.onClick(this);
	}
}

And using it is just like before:

myButton.setOnClickListener(new MyButton.OnClickListener() {
	@Override
	public void onClick(Object o) {
		Log.v("", "Object " + o + " was clicked);
	}
});

Notice how the name is used when instantiating (new MyButton.OnClickListener()) to avoid collisions. I’ve seen this in several differenr packages (mainly Android’s own framework) and it’s a pretty clever solution to avoid name collision given that so many events can share the same name.

All in all, despite the syntactical differences, event dispatching in Java works the same as in ActionScript 3, expect for one major caveat: listeners are normally set, not added. This means you cannot have more than one listener to the same event on the same object.

Personally, I’ve added a multi-listener feature to some of my classes (it’s just a matter of adding to an ArrayList afterall), but the real problem is that none of the established classes follow that pattern. So… be careful when adding your listeners in Java, lest you erase a listener that was set previously.

4 responses

Comments are closed.