php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Callbacks in ActionScript

Posted by on June 7, 2010

This post is going to be long on code. That means that I will spinkle little text between the code blocks to explain what I am doing but the bulk of the post will be code for you to copy, paste and mangle as you see fit. If you don’t want to copy and paste everything, here is the project in an archive for you to play with. twallpaper (NOTE: This project, while it compiles, is incomplete. Stay tuned for future updates.)

Callbacks rock in ActionScript

I am the first to admit that I am still trying to get my head around the concepts behind event driven programs. I’ve been knee deep in web for so long that a lot of these concepts are still not coming to me. One of the problems I am facing is how to different components, that know nothing of each other, talk to each other? Also, with asynchronous tasks taking place, how does component A know that component B has completed it’s task. The answer to both of those questions is ‘callbacks’.

In PHP we use the Observer pattern in cases to solve problems. Callbacks are an implementation of the Observer pattern in that one component tell another component, “tell me when you are done” or “tell me when something changes”.

My current project

I want to write an AIR program that looks at the twitter stream, searches for images, (I’ll use yfrog but you can easily add twitpic or any others) caches them locally and then puts them up on the screen with a nice little animation. What I have right now is a very ugly application that fetches the last 20 tweets that mention yfrog.com. The point of this milestone was to show that I could separate the logic into classes and have the classes talk to each other.

TweetFetch

Ok, here comes the first batch of code. This is the main class that talks to twitter. It is in no way a complete twitter API wrapper for ActionScript. It simply calls the search, stores the results and processes callbacks.

package com.calevans
{
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;
	import mx.controls.Alert;
	import mx.collections.ArrayCollection;

/*
 * Fetchs all the images in the current timeline.
 */

	public class TweetFetch
	{
		protected var _service:HTTPService;
		protected var _data:ArrayCollection;
		protected var _responseCallbacks:Array;
		protected var _faultCallbacks:Array;

		public function TweetFetch()
		{
			trace('TweetFetch');
			this._service = new HTTPService();
			this._responseCallbacks = new Array();
			this._faultCallbacks = new Array();
			this._service.url = 'http://search.twitter.com/search.atom';
			this._service.addEventListener(ResultEvent.RESULT, response );
			this._service.addEventListener(FaultEvent.FAULT, fault );
			this._service.showBusyCursor=true;
		} // public function TweetFetch()

		public function fetch(query:String):void
		{
			trace('TweetFetch::fetch');
			var params:Object = new Object();
			params['q'] = query;
			this._service.send(params);
			return;
		} // public function fetch(query:String)

		public function response(response:Object):void
		{
			trace('TweetFetch::response');
			this._data=response.result.feed.entry;
			if (this._responseCallbacks.length>0) {
				for (var i:int=0;i0) {
				for (var i:int=0;i<this._faultCallbacks.length;i++) {
					this._faultCallbacks[i](e);
				}
			} else {
				Alert.show(e.toString());
			}
		} // public function myfault(e:FaultEvent):void

		public function registerResponseCallback(f:Function):void
		{
			trace("TweetFetch::registerResponseCallback");
			this._responseCallbacks[this._responseCallbacks.length] = f;
			return;
		} // public function registerResponseCallback(f:Function):void

		public function registerFaultCallback(f:Function):void
		{
			trace("TweetFetch::registerFaultCallback");
			this._faultCallbacks[this._faultCallbacks.length] = f;
			return;
		} // public function registerFaultCallback(f:Function):void

	} // public class TweetFetch

} // package com.calevans

One of the first things I hope you notice is all the trace() calls. Because we are dealing with events and async processes, I find it easier to understand what is going on if I put a trace at the beginning of each method. That way I can actually see the order of execution.

The code above works. It worked about 5 minutes after I wrote it (I did have a couple of errors working with the API) but I didn’t understand how to hook it back into the main application. That is when I started reading up on callbacks.

Functions are objects…just like everything else

One big difference between PHP and ActionScript (and I beleive JavaScript) is that functions are objects in ActionScript. This means that I can pass a function as a parameter. I’ve not investigated deeply but I beleive these are passed by reference and not by value. Therefore, I can not only pass a function in as a parameter of another function, I can store that function reference, pass it around and even call it. That, in my book, is pretty dang cool. (I am easily amused)

You will see in the cod above a function registerResponseCallback() (and it’s conunterpart registerFaultCallback()) As the name says, this is were you tell TweetFetch what to do once it has successfully fetched and stored the tweets. I will show the full twallpaper.mxml later, for now, here is the snippet form it where I actually register a callback.

tweetFetch.registerResponseCallback(processNewTweets);

It is that easy, well almost. There are a couple of things you need to know. First, obviously processNewTweets() needs to exist. Second, it need to accept the proper parameters. In this case, when the callbacks are processed, they will each be called, passing in an ArrayCollection so processNewTweets() has to accept a single parameter of an ArrayCollection.

fetch() at line 32, is the main function of this method. Calling hat with a query will set things into motion. It makes the call to the HTTPService. When HTTPService returns successfully, it fires result(). result() stores a subset of the result that comes back from twitter and then looks to see if there are any registered callbacks. If there are, it calls them with this line:

this._responseCallbacks[i](this._data);

this._responseCallbacks[i] this contains the pointer to the function. (this._data) This is the parameter list to pass in. This is what I meant about callbacks being cool, it is that easy.

Do the deed

Ok, you understand the code, you see now how to create a callback that TweetFetch can fire so that the interface can be updated as things happen. Here now, is the main twallpaper.mxml so you can see how I put it all together.

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="init(event)">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->

	</fx:Declarations>
<fx:Script>
	<![CDATA[
		import com.calevans.TweetFetch;
		import mx.events.FlexEvent;
		import mx.collections.ArrayCollection;
		import mx.rpc.events.FaultEvent;
		import mx.controls.Alert;

		public var tweetFetch:TweetFetch = new TweetFetch();

		protected function init(event:FlexEvent):void
		{
			trace('init');
			tweetFetch.registerResponseCallback(processNewTweets);
			tweetFetch.registerFaultCallback(oops);
		}

		public function mainButtonClick():void
		{
			trace('mainButtonClick');
			tweetFetch.fetch('yfrog.com');
		}

		protected function oops(e:FaultEvent):void
		{
			trace('oops');
			Alert.show(e.toString());
			return;
		}
		protected function processNewTweets(data:ArrayCollection):void
		{
			trace('processNewTweets');
			for(var i:int=0;i
</fx:Script>
	<s:Button x="251" y="83" label="Button" click="mainButtonClick()" />
	<s:TextArea id="myTextArea" x="0" y="125" width="573" height="490"/>
</s:WindowedApplication>

I have said it before but I will reiterate it this is prototype code. The current interface is only to show that TweetFetch is working. So yes, it’s ugly. It does show that the underlying code is working however and it shows a very simple example of callbacks.

One note, I have implemented callbacks for faults as well. Just to show that they are working, I added the function oops() that simply pops an alert box, the same behavior that happens if there is no registered callback. This is just to show the functionality, neither oops(), nor the Alert box will survive into the final code.

Wrapping it up

The problem I had in this particular instance was, how do you update the interface when an async task in an object completes. In my previous examples, all my code was in the main mxml for ease of understanding but as I began to move things out into classes and began to normalize my application, I found new problems presenting themselves. Events, which I will talk about in another post, and callbacks make solving those problems very easy. As this starts to sink into to my web-soaked brain, things start to make sense.


Cal Evans is a veteran of the browser wars. (BW-I, the big one) He has been programming for more years than he likes to remember but for the past [redacted] years he's been working strictly with PHP, MySQL and their friends. Cal regularly speaks at PHP users groups and conferences, writes articles and wanders the net looking for trouble to cause. He blogs on an "as he feels like it" basis at Postcards from my life.
Tags: , , , ,
 

Responses and Pingbacks

Actually I do believe that functions are objects in javascript as well.
You can write stuff like
var test = function() {};
test.myProperty = “Hello world!”;
alert(test.myProperty);
and the result will be Hello world!

Functions are a special type of object though so typeof will return function.

Marcus,

Yes, functions are objects in JavaScript as well. JavaScript and ActionScript share a lot of common features.

=C=

Oh sorry I misread you Cal.

I read that sentence as saying that in Actionscript functions are objects but not in javascript but when reading it again I see that you’re contrasting js and as to php which makes alot of sense of course.

Well, this is possible. But Flex has built-in system of dispatching and handling events. And I think it’s much cleaner/nicer. When one operation is done, you do something like dispatchEvent(new Event(blablabla)) and then handle the event in code where you need it. You can also build your own Event class and pass the data which is required during handling the event.

Leave a comment

Use the form below to leave a comment: