Coding for Callbacks - Development of Event Driven Apps

As with jQuery development, asynchronous event driven code is a powerful way to write dynamic code. The Vapor Android framework provides two ways to register for event notifications in your apps, namely callbacks and hooks.

Hooks are a very deep, flexible and powerful feature of Vapor. As such, they warrant their own section which app developers are advised to read to fully understand their role and usage. This section is about callbacks and listeners in the Vapor Android framework.

Single Dollar Prefix ($____)

In Vapor you will often come across type names prefixed with a single dollar ($____). These are listener interfaces that pertain to an event or callback, and in several cases purely just convenient short type aliases for existing Android interfaces.

For example $click is an alias for the Android OnClickListener interface. These aliases are useful because they are much shorter to write during Android app development, but importantly, still just as readable.

You can use an implementation of an applicable single-dollar listener anywhere that expects an implementation of the original Android interface.

Double Dollar Prefix ($$____)

The Vapor Android framework supports so-called 'One-Size-Fits-All' listeners, denoted by a $$____ prefix. These listeners are convenient in Android app development because they unify related, but separate interfaces in a single class.

For example, $$checked is a 'One-Size-Fits-All' listener for the checked event because it unifies the compoundbutton.$checked and radiogroup.$checked interfaces in one object.

The methods from the unified interfaces are 'stubbed' out, by way of default implementations that don't do anything useful. This allows the developer to only provide implementations for the methods that are needed, thus your Android app code won't be littered with the methods you don't.

Also, double-dollar listeners indirectly implement the original Android interfaces they unify. As a result, you can use applicable double-dollar listeners anywhere that expects an implementation of the original Android interfaces.

A neat consequence of 'One-Size-Fits-All' listeners is the app development time you save not having to hunt for the correct package namespace that your desired listener is defined in.

For example, if you want to have a focus callback on a ViewTreeObserver you just use $$focus, you don't need to qualify the usage with vapor.listeners.view.viewtreeobserver.$focus!

For convenience, all 'One-Size-Fits-All' listeners related to View events are defined in the root of the vapor.listeners package hierarchy

One-Size-Fits-All (Double Dollar) Listener Example

Now let's say a part of your Android app had an interest in two different interfaces that are unified by a double-dollar listener. In this situation you can just write all of your code in the one double-dollar object and use it in places that expected implementations of either interface.

For example, if we needed to listen out for both vapor.listeners.view.$focus and vapor.listeners.view.viewtreeobserver.$focus callbacks we could just create one $$focus object and use that instead:

	$$focus focus = new $$focus(){

		@Override
		public void onFocusChange(View arg0, boolean arg1) {

			Log.i("onFocusChange(View,boolean)", "I'm interested in this event...");

		}

		@Override
		public void onGlobalFocusChanged(View arg0, View arg1) {

			Log.i("onGlobalFocusChanged(View,View)", "...And this one!");

			Log.i("Hey Presto!", "We only need one object to store both implementations!");
		}

	};
In the majority of cases, the single-dollar ($____) interface types have the same class name as the companion double-dollar ($$____) types, but with a single-dollar prefix - e.g. $click and $$click

With the above overview in mind we are now ready to consider another example:

	$.Button(R.id.myButton).click(new $$click(){

		public void onClick(View v){

			Log.i("myButton.onClick", "myButton was just clicked!");
		}

	}).text("Click me!"); // chained methods like jQuery!
	Button button = (Button)findViewById(R.id.myButton);

	button.addOnClickListener(new OnClickListener(){

		public void onClick(View v){

			Log.i("myButton.onClick", "myButton was just clicked!");

		}

	});

	button.setText("Click me!");

The Vapor framework code is not only much more compact, but all method calls are done in a fluent chain - like in jQuery.

Indeed, the last call to .text(...) in this chain will return the updated VButton instance so it could either be chained further, stored in a VButton variable, or passed directly as a parameter!

If you are developing an Android app where memory is at a premium you may wish to use the leaner single-dollar ($____) interface types instead of double-dollar types ($$____)

Custom Callbacks

We've seen above how the Vapor Android framework provides short, readable aliases for common Android interfaces and listeners, but often you want to define your own callbacks during development.

You could create an interface for your new callback, and even a Vapor inspired type alias prefixed with dollars. However, the Vapor Hook Framework provides the tools to quickly and easily create custom callbacks, without the need for extra .java source files in your project.

Let's visualise the comparison:

	public static final String CUSTOM_CALLBACK = "customCallback";

	private CustomObserver customObserver = new CustomObserver;
	...
	
	$.hooks(CUSTOM_CALLBACK).hookIn(new $$hookee<CustomObserver>(customObserver){
	
		public void call(String hookName, VaporBundle args){
		
			String callbackMessage = args.getString("callbackMessage");
		
			$subject.message(callbackMessage);
			
		}
	
	});
	
	...
	
	$.hooks(CUSTOM_CALLBACK).fire(
		$.Bundle().put("callbackMessage", "Finally, a callback! I've waited ages...!")

	); // invoke callback from anywhere statically

Now, let's see how you could achieve similar functionality in standard Android/Java:

	public interface CustomCallback{

		public void customMethod(String callbackMessage);
	}
	public interface $custom extends CustomCallback{}
	public class CustomObserver implements $custom{
		private String message;
		
		...
		
		@Override
		public void customMethod(String callbackMessage){
			this.message(callbackMessage);
		}
		
		public void message(String newMessage){
			this.message = newMessage;
		}
	}
	private CustomObserver customObserver = new CustomObserver;
	
	...
	
	public void someAsyncCallback(CustomCallback callback){
	
		if(someCondition) 
            callback.customMethod("Finally, a callback! I've waited ages...!");
	}
	
	...
	
	someAsyncCallback(customObserver);

The benefits of the Vapor way:

  • We do not need to create extra source files that bloat our Android application
  • We can fire the callback statically from anywhere in our Android application, and even password protect it from being misfired
  • We can arbitrarily add new data to the VaporBundle sent to observers at any time, unlike the standard Java way which would require us to rewrite the method signatures of the interface
  • We can use the special $subject variable to reference the original observer (given the same type as the observer), even if the original observer object has since gone out of scope

From looking at the benefits one could see the Vapor Hook Framework as a perfect solution, but it is not without a trade off. Namely, CustomObserver cannot be polymorphically treated as an implementation of CustomCallback.

As with all development, you need to assess your requirements and choose the best approach for your scenario.

Read the VaporHooks section for more information.