Enforcing Abstract Classes in AS3

One of the most common criticisms of ActionScript is that it doesn’t include some of the more advance features of other object-oriented languages. For example, Abstract classes. Abstract classes are similar to Interfaces in that they provide a common API for: implementers of the interface or subclasses of the Abstract class. At the same time, Interfaces and Abstract classes guarantee this common API for classes from other packages to work against. The Abstract class takes it one step beyond the Interface in that it also adds real functionality to the base class which can reused in its subclasses.

So if ActionScript doesn’t enforce the Abstract class, how can you guarantee that someone down the line doesn’t start abusing it? A passable answer to this question is the seldom-used “self” keyword, as seen here:

public function AbstractClass(self:AbstractClass)
{
	if(self != this)
	{
		throw new Error("Whoa!  You can't instantiate AbstractClass directly suckah!");
	}
}

The drawback to this method is that it’s not enforced at compile-time. Instead, the error will be thrown at runtime and alert the developer that they did something wrong. Further, there’s nothing stopping the developer from then catching the error and ignoring it. Ruh roh. So there is no real solution that guarantees the Abstract nature of the class is respected, but at least this little warning will deter all but the most irresponsible of programmers.

AS3 Reference on Your iPhone

This has flown under my radar since January, but now that I know it exists, its a must-have! I’ve played with it only briefly so far and noticed that it only includes the class references, more obscure things like keywords aren’t included, but it’s still super handy nonetheless.

Because all the data is stored locally, you can use it while you’re on the go without connectivity, and searching is super fast, even faster than opening a browser and searching if you have your phone handy.

http://www.mikechambers.com/blog/2009/01/26/actionscript-3-reference-for-iphone/

AS3 Reference for iPhone

Flash Camp Atlanta

I just signed up for Flash Camp Atlanta! It’s been a while since my last conference, the Adobe onAir Tour, so I’m very excited to spend another day geeking out with fellow Flash developers. Although I’d like to see more Adobe employees, the guests, speakers, and sessions do look like they’ll be interesting.

The networking opportunities are what’s really gotten me excited though. Most of the people I work with are all much more experienced than I am and have been around the block a few times, so they know all the names around the interactive industry in Atlanta. I want to put some faces with those names, as well as spread my own around a bit.

Flash Camp Atlanta Badge
FlashCampAtlanta.com

Using a Simple PHP Proxy to Load Data Across Domains

The Flash Player doesn’t allow you to load and use data from other domains, even a subdomain of where you SWF is being served from. Normally this can be worked around by editing the policy file, crossdomain.xml, of the domain where the data is located, but what can you do when the data you want to load is owned by someone like Facebook or Twitter? Chances are that you don’t have the kind of pull it would take to get them to change their policy files, so it’s time to implement a web service on the domain where the SWF is hosted.

I ran into this problem while working with the Twitter and Facebook APIs on my site. Their APIs allow you to get names, URLs, and other interesting data points, but I wanted to load images, which aren’t delivered through the API. To solve this problem, I wrote this dead-simple PHP script which simply forwards GET requests to a URL specified in the “url” parameter and displays the result back to the requester:

<?php
     $content = file_get_contents($_GET['url']);
 
     if ($content !== false) 
     {   
          echo($content);
     } 
     else 
     {  
          // there was an error
     }
?>

Of course this is the most simple of examples of a web service that you can get, and is really only useful for simple content like XML, HTML and images. Anything more complicated than this and you would want to create a proper web service with input validation, caching, etc., but this is a quick way to get up and running.

Note that your browser won’t render the proxy-ed image correctly because it doesn’t receive the correct content-type header, which tells the browser to expect an image. What you’re seeing is actually the ASCII representation of the image’s binary data, known as BitmapData to the AS3 world.

Bonus! It can be slow to go through your proxy when not needed, so I use this simple class to create URLRequests with the proper URL as determined by the SWF’s security sandbox:

package com.bdement.proxy
{
import flash.net.URLRequest;
import flash.system.Security;
 
public class ProxyService
{
	public static const PROXY_URL:String = "http://www.brandondement.com/proxy.php?url=";		
 
	public function ProxyService()
	{
		throw new Error("All methods of ProxyService are static, do not instantiate!");	
	}
 
	public static function getProxyRequest(url:String) : URLRequest
	{
		if(url == null) return new URLRequest();
		return new URLRequest((isLocal ? "" : PROXY_URL) + url);
	}
 
	public static function toProxySource(source:String) : String
	{			
		if(source == null) return null;
		return (isLocal ? "" : PROXY_URL) + source;
	}
 
	public static function get isLocal() : Boolean
	{
		return	(	Security.sandboxType == Security.LOCAL_TRUSTED 
			||	Security.sandboxType == Security.LOCAL_WITH_NETWORK);
	} 
}
}

Creating an Image Cache with ActionScript 3

A Flash application that loads images at runtime and uses them over and over won’t attempt to reuse any previously downloaded assets. Instead, the Flash Player will download a new image from the server over and over, wasting time and bandwidth. The additive effect of this can create a real noticeable difference in performance on a large scale.

For example, we ran into this problem on the leaderboard of the PGA Tour Shot Tracker. The leaderboard displays the player’s name and the flag of the country they’re from. There can be hundreds of players in the tournament, but they all come from about 2 dozen countries or so. This setup is a perfect use case for caching images internally.

I started looking around for a solution, and came across this question on StackOverflow.com as well as an excellent pseudo-code example: this blog post.

With about an hour’s worth of free time I created a fully functioning ActionScript image cache of my own. There are only 2 parts:

  1. CachedImage.as – A subclass of the Image control. A CachedImage adds itself to the cache when its COMPLETE event is fired.
  2. ImageCache.as – Stores the BitMapData of the CachedImages in a dictionary and provides a small set of static methods for querying, adding, and retrieving from the cache.

CachedImage.as

package com.bdement.imagecache
{
import flash.events.Event;
 
import mx.controls.Image;
 
public class CachedImage extends Image
{
	private function onComplete(event:Event) : void
	{		
		ImageCache.add(this);
	} 
 
	public override function set source(source:Object) : void
	{
		if(!ImageCache.contains(source)) {
			addEventListener(Event.COMPLETE, onComplete); 
		}			
		super.source = ImageCache.get(source);
	}		
}
}

ImageCache.as

package com.bdement.imagecache
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.utils.Dictionary;
 
/**
 * 
 * @author bdement
 * 
 * This class reduces the amount of HTTP requests your application has to make
 * by storing the BitMapData of CachedImage objects in memory for reuse
 * by images created after the first one completes.
 */
public class ImageCache
{
	/**
	 * The internal representation of the cache
	 */		
	private static var cache:Dictionary = new Dictionary(false);				
	/**
	 * All the methods of ImageCache are static, so the constructor shouldn't be called.
	 * 
	 */				
	public function ImageCache()
	{
		throw new Error("You don't need to instantiate the ImageCache!");
	}	
	/**
	 * 
	 * @param imageSource The source of an image
	 * @return Whether the cache already contains the image indicated by imageSource 
	 * 
	 */				
	public static function contains(imageSource:Object) : Boolean
	{
		return cache[imageSource] != null;
	}		
	/**
	 * Retrieves the image 
	 * 
	 * @param imageSource The source of an image
	 * @return Either: The image if it exists in the cache, OR the default
	 * source that was passed in via imageSource.
	 * 
	 */		
	public static function get(imageSource:Object) : Object
	{
		if(contains(imageSource)) {
			trace("using '" + imageSource + "' from cache!");
			return new Bitmap(cache[imageSource] as BitmapData);	
		}
		return imageSource; // String or Object
	}
	/**
	 * Adds an image to the cache
	 * @param image The image to be cached 
	 * 
	 */		
	public static function add(image:CachedImage) : void
	{			
	    if (!contains(image.source))
	    {
	    	trace("adding '" + image.source + "' to cache!");
	        var bitmapData:BitmapData = 
	        	        new BitmapData(	image.content.width, 
	        	                                        image.content.height,
                                                                true,
	        		                                0x00000000);
 
	        bitmapData.draw (image.content);
 
	        cache[image.source] = bitmapData;
	    }
	}
 
}
}

Finally, you leverage the ImageCache and CachedImage in your application. You can set the source of your CachedImages in all the ways you normally would, and the ImageCache will be used automatically. Note that our ImageCache doesn’t cache embedded images. This is because embedded images don’t fire the COMPLETE event, which is OK because they’re already cached in memory and they can be reused without using our ImageCache.

Application.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
	xmlns:mx="http://www.adobe.com/2006/mxml" 
	xmlns="*"
	width="500" height="200"
	layout="horizontal"	
 >  
	<CachedImage id="image1" source="http://www.brandondement.com/blog/wp-content/uploads/2009/08/rooster.gif" />			
 
	<mx:VBox>
		<mx:Label text="Set image2.source to..." />
		<mx:Button click="{image2.source = image1.source}" label="image1.source" toolTip="image1.source" />
		<mx:Button click="{image2.source = 'http://www.brandondement.com/blog/wp-content/uploads/2009/08/rooster.gif'}"	label="HTTP URL" toolTip="http://www.brandondement.com/blog/wp-content/uploads/2009/08/rooster.gif" />
		<mx:Button click="{image2.source = null}"	label="null" />
	</mx:VBox>
 
	<CachedImage id="image2" />
 
</mx:Application>

And finally, here’s the working example. To doublecheck that it’s actually using the cache, reload this page with a web debugging proxy on, like Charles. You’ll notice a request for “rooster.gif,” which is the flash movie downloading the image on the left. (There will probably be two, since there are two example files) But notice that when you click the buttons below, no more requests are made. Success!

Get Adobe Flash player

And here, just for comparison’s sake, is the example using normal mx:Images without caching built in. With Charles on you’ll notice that every time the image source is changed from null, a new request is made!

Get Adobe Flash player

Creating an Image with Rounded Corners

I recently wanted to round the corners on my images for my website, and the component I found on Flex Examples wasn’t doing the trick when I used it as an ItemRenderer for a TileList, where I was setting the maxWidth and maxHeight. After some toiling I tracked down the issue and produced this:

package com.bdement.components.images
{
	import flash.display.Sprite;
	import flash.events.Event;
 
	import mx.controls.Image;
	import mx.events.ResizeEvent;
 
	public class RoundedCornerImage extends Image
	{		
		[Bindable] public var cornerRadius:uint = 10;
 
		private var roundedMask:Sprite;
 
		public function RoundedCornerImage()
		{
			super();
		}
 
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
	    {   
	    	super.updateDisplayList(unscaledWidth, unscaledHeight);
 
	    	if(!roundedMask)
	    	{
	    		roundedMask = new Sprite();
	    		addChild(roundedMask);    
	    	}
 
	       	roundedMask.graphics.clear();
	        roundedMask.graphics.beginFill(0xFF0000);
	        roundedMask.graphics.drawRoundRect(0, 0,
	                    contentWidth, contentHeight,
	                    cornerRadius, cornerRadius);
	        roundedMask.graphics.endFill();
 
	        this.mask = roundedMask;
	    }
	}    
}

Works like a charm!

Side note: Did you know that rounded corners are easier for the eyes to perceive? This interesting article about the history of rounded corners makes mention of it.