Developing Flash Applications For Use in the Browser and AIR Using Factory Programming Patterns

WARNING: This was written for a BETA version of AIR and may not be valid for the most recent release

Web developers love AIR because it leverages web technologies, like HTML/JavaScript and Flash, to develop desktop applications. This lets web developers develop applications for the desktop using the same tools and same practices used when developing content for the web.

Because AIR uses web technologies in this manner, it is easy to port existing web applications to the desktop. Additionally, applications can be built once to function both on the web and on the desktop through AIR; a separate project is not be necessary to maintain a desktop version of an application that will also exist in the browser. This can be easily achieved through the use of programming patterns.

Patterns in programming are structured approaches used to perform a necessary task. This tutorial will show you how applications can be developed for both the web and the AIR runtime using a programming pattern known as the factory pattern.

Requirements:

Note: Though this tutorial uses Flash CS3, the same ActionScript could be applied to an AIR project in Flex Builder.

Source files:

Factory Patterns

Factory patterns are creational patterns used to delegate the creation of objects in code. Instead of creating an instance of a class directly using new keyword in the context in which it is needed, a factory pattern can be used to generate an instance for you. The advantage of using a factory in this manner is that the instance generated can be different based on the needs of your application or the current state of the program or operating system, etc.

In Flash, when building an application that will work on both the web in a browser and in AIR for the desktop, a factory pattern can be used to generate class instances specific to the container of the player (browser or AIR) for classes or operations that need to be specific for each. Saving a file, for example, is something that would be handled differently in both a browser based application and an application based in AIR. Separate classes can be coded to handle those operations for both cases. The factory is used to simplify the creation of those classes dependant on which container the applications is in. If the player is being played in the browser, an instance of one class will be generated; if being played in AIR, another class instance will be created. The responsibility of determining which is being used is encapsulated within the coding of the factory class making working with the saving class (which ever class is created) easier.

Interfaces and Abstract Classes

Because factories produce different instances based on different circumstances, it's important that those classes, despite their differences, can be used interchangeably. No matter which instance is created from a factory, you should be able to use it the same way as if some other instance was created instead. For this to be true, the instances created need to share the same interface or, in other words, have the same properties and methods which are universally accessible to the client application (or whoever created the instance) so that they can each be used the same way. For this to be true, the classes used to generate these instances would need to either extend the same base class (often an abstract class - a class that is not instantiated on its own but only used for other classes to extend) or have to implement the same interface.

If you've never understood the uses of interfaces before, here's a good place to start. Interface definitions are used to define and enforce the interface of classes - that being the publically accessible methods and properties those classes contain. Interface definitions also also define a type. So instead of creating a variable typed as the class type of an instance, you can instead type it as the interface (or one of multiple) the class implements. If you have two different classes that implement the same interface, a single variable can be typed using that interface and legally be used to reference both instances.

// SomeClass implements SomeInterface
var holder:SomeInterface = new SomeClass();

The same is similar with abstract superclasses - or even superclasses that aren't abstract. Any variable can be typed as a superclass and be given values of its subclasses.

// SubClass extends SuperClass
var holder:SuperClass = new SubClass(); 

Interfaces have the advantage over superclasses in that you aren't required to have a class extend that particular superclass to gain its type. For example, if you wanted to make a class based on a superclass that extended the flash.geom.Rectangle class, but needed it to be a display object, you would have a problem. For a class to be a display object, you have to inherit from flash.display.DisplayObject meaning extending Sprite or MovieClip or any one of their subclasses. If the superclass you need to extend is based on Rectangle, a subclass won't have display object functionality.

Subclassing, however, does allow you to inherit functionality. Since interfaces only indicate what methods or properties need to exist and don't implement them, each class implementing an interface will need to have their own implementations. Subclassing reduces that work since shared functionality can be centralized in one single superclass.

Note: ActionScript 3 does not support abstract classes explicitly. Abstract classes in terms of Flash is more about how they are used (i.e. not instantiated, only extended).

The Factory Method and Saving BMP Files

The factory method is a factory pattern that uses a method or function to create an instance of a certain type based on some kind of condition. All instances created by this method would either implement the same interface or be a subclass of a common superclass class, usually abstract. The interface or superclass would then be used as the methods return type and the type used to define any variable containing the instance returned.

Using the factory method we can create a single Flash application that can be used both in the browser and as an AIR application that, depending on the player type, will save a BMP image file in different ways specific to that player type. The factory method will be used to create an instance of one of two subclasses that both extend a common abstract superclass used to define a methods both subclasses use.

Creating the Interface

  1. Create a new AIR application in Flash CS3
  2. Save the application as SaveAsBMP.fla
  3. Using File > Import > Import to Stage... find and import coloredpenciltips.jpg from the source files. You can also use a custom image if you prefer
  4. Convert the image to a movie clip, saving it into your library using any symbol name you want. Make sure the registration point is to the upper left
  5. Give the new movie clip instance on the stage the instance name imageMovieClip
  6. Beneath the image add a button component (or create your own button or movie clip). This will be clicked to save your bitmap
  7. Give the new button the instance name saveButton
    ]

The imageMovieClip instance is the instance from which the bitmap image will be generated. Clicking saveButton will use an instance created by a factory method to save that image. The type of instance created by the factory will depend on whether or not the application is being run in the browser or AIR. Each instance, whether for the browser or AIR, will extend the same abstract superclass.

Bitmap Saving Classes

Four classes will be needed to handle the bitmap saving in this example. First there is the abstract class that defines all bitmap saving classes created by the factory. It will be called AbstractBitmapSaver. Next there are the two classes that subclass that to handle either browser-based saving or AIR-based saving, NetBitmapSaver and AIRBitmapSaver (respectively). A fourth class is needed to convert a Flash bitmap image in to an actual bitmap (BMP) file.

The BMP format was chosen for this example because its a very easy format to encode images to, especially from Flash. A BMPEncoder class has been provided in the source files (com.senocular.images.BMPEncoder) to handle this process for you. Usage is simply as follows:

import com.senocular.images.BMPEncoder;
var myBMPBinary:ByteArray = BMPEncoder.encode(myBitmapData);

Given that our bitmap saving classes will all extend AbstractBitmapSaver, that class can encapsulate that functionality into a protected method that the derived classes can easily access and use. In addition to providing common functionality such as this, and really what the abstract superclass is really meant to be doing for factories, the AbstractBitmapSaver should also be defining the interface, or what methods and properties subclasses should be using or have access to.

For this example, we only need one method for our save classes, a save() method. This method will take a BitmapData instance and save it to the users computer through whatever means available based on the player container. That method is defined in the abstract superclass and then overridden in subclasses to provide any extra (or the full) functionality required.

AbstractBitmapSaver class:

package {
	
	import flash.display.BitmapData;
	import flash.utils.ByteArray;

	import com.senocular.images.BMPEncoder;

	
	public class AbstractBitmapSaver {
		
		public function AbstractBitmapSaver() {
			if (Object(this).constructor == AbstractBitmapSaver) {
				throw new ArgumentError("AbstractBitmapSaver class cannot be instantiated.");
			}
		}
		
		public function save(bitmapData:BitmapData):void {
		}
		
		protected function encode(bitmapData:BitmapData):ByteArray {
			return BMPEncoder.encode(bitmapData);
		}
	}
}

Along with the save method, which here, in the superclass, does nothing, is the encode() method using the BMPEncoder class. Subclasses will not have to worry about using the BMPEncoder directly, instead just calling the inherited encode() method to get the BMP file bits.

In the AbstractBitmapSaver constructor there's some additional code that is being used to ensure that the class is used as an abstract class and not being instantiated. If anyone tried to make an instance of AbstractBitmapSaver directly, a runtime error will be thrown. The class can still be extended by subclasses without error. This code is not necessary but can be helpful in development if you fear the class may be used incorrectly.

The NetBitmapSaver subclass will save the bitmap in this application using server-based if being run in the browser (or, really, anything other than in AIR). The implementation here will simply use a PHP file (download.php) to push the download through the browser through a raw POST of the bitmap data using navigateToURL(). Depending on your backend, another server-side solution could be similarly used.

NetBitmapSaver class:

package {
	
	import flash.display.BitmapData;
	import flash.net.navigateToURL;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	
	public class NetBitmapSaver extends AbstractBitmapSaver {
		
		public function NetBitmapSaver(){
		}
		
		public override function save(bitmapData:BitmapData):void {
			if (bitmapData == null) return;
			var request:URLRequest = new URLRequest("download.php?filename=image.bmp");
			request.method = URLRequestMethod.POST;
			request.data = encode(bitmapData);
			navigateToURL(request);
		}
	}
}

Notice that the save method is being overridden to provide unique save functionality to the NetBitmapSaver class. The same is done with the AIRBitmapSaver class providing the same action with a different approach, one specific to AIR.

AIRBitmapSaver class:

package {
	
	import flash.display.BitmapData;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	
	public class AIRBitmapSaver extends AbstractBitmapSaver {
		
		private var saveFile:File = new File();
		private var bmp:ByteArray;
			
		public function AIRBitmapSaver(){
			saveFile.addEventListener(Event.SELECT, saveFileSelect);
		}
			
		public override function save(bitmapData:BitmapData):void {
			if (bitmapData == null) return;
			bmp = encode(bitmapData);
			saveFile.browseForSave("Save");			
		}
		
		private function saveFileSelect(event:Event):void  {
			var stream:FileStream = new FileStream();
			stream.open(saveFile, FileMode.WRITE);
			stream.writeBytes(bmp);
			stream.close();
		}
	}
}

For AIR, the File (flash.filesystem.File) and FileStream (flash.filesystem.FileStream) classes are used to save the bitmap file. These are used to save the file from the Flash Player in AIR directly to the user's computer without the reliance on a server. Using an instance of the File class (saveFile) you can invoke a save dialog using the browseForSave() method. This will let the user browse for a location on their hard drive associating that location with the saveFile instance. Once selected, the Event.SELECT event will fire and a FileStream instance can be used to save the file (Event.CANCEL will be called if the user cancels the browse window but there is no need to have a listener for that in this example).

The FileStream class is what actually saves a file to the users computer in AIR. The open() method essentially opens a file or file stream on the computer (different means of handling data can be specified using the constants in the FileMode class (flash.filesystem.FileMode)). Next writeBytes() is given the binary BMP data to send it and write it to the location of the file system opened. Finally, close() is used to close that file once data has been written.

  1. From the source files, copy AbstractBitmapSaver.as, NetBitmapSaver.as, AIRBitmapSaver.as, and the com folder (container BMPEncoder.as) into the directory where you saved SaveAsBMP.fla
  2. Open SaveAsBMP.fla in Flash
  3. Select the first frame on the timeline and, if not already open, open the Actions panel using Window > Actions

The Factory Method

Now it is time to create the factory method that creates the correct class instance based on the player container. The player container can be identified in Flash using the playerType property of the Capabilities class (flash.system.Capabilities). When in AIR, its value will equal "Desktop". A method called getBitmapSaver can be made to check for that value and instantiate the correct subclass of AbstractBitmapSaver for the current player.

  1. Add the following script to the Actions panel (frame 1 timeline script)
    function getBitmapSaver():AbstractBitmapSaver {
    	var saver:AbstractBitmapSaver;
    	switch(Capabilities.playerType) {
    		case "Desktop":
    			saver = new AIRBitmapSaver();
    			break;
    			
    		case "PlugIn":
    		case "ActiveX":
    		default:
    			saver = new NetBitmapSaver();
    			break;
    	}
    	
    	return saver;
    }

When the player container type is identified and the proper subclass instance is created, it is returned to the caller typed as AbstractBitmapSaver. Since AbstractBitmapSaver contains the save() method definition, typing with that class works for cases where save() is used with any of the subclasses created. For this example, that would happen in the CLICK event handler for saveButton.

  1. Add the following additional script to the Actions panel (frame 1 timeline script)
    saveButton.addEventListener(MouseEvent.CLICK, saveImage);
    function saveImage(event:MouseEvent):void {
    	
    	var bitmapData:BitmapData = new BitmapData(imageMovieClip.width, imageMovieClip.height);
    	bitmapData.draw(imageMovieClip);
    	var saver:AbstractBitmapSaver = getBitmapSaver();
    	saver.save(bitmapData);
    }

Once the saveImage button is clicked, a BitmapData instance is created from the imageMovieClip movie clip on the screen. Then, using the instance returned from getBitmapSaver(), the factory method, that bitmap data is saved.

Though the CLICK event handler is technically doing the save work, it does not have to worry about what player container the SWF is currently running in. That logic is delegated to the factory method and the details handled within the different subclasses used to save the data for each situation.

  1. Test your movie inside of the Flash Authoring environment. Since it was created as an AIR-based application, it should open in ADL (using AIR)
  2. Click on saveButton to save the image as a BMP file; you should be prompted to save using the save dialog from the File class
  3. Next publish your movie with HTML
  4. Run the HTML file on a server including the download.php file in the same directory as the SWF and HTML (or through an alternate server-side approach - be sure to modify NetBitmapSaver.as accordingly if doing so)
  5. Click on saveButton in the SWF running in the HTML page to save the image as a BMP file; a browser window should load prompting you to save using the browser's file download dialog

And like that you have an application built with one SWF that will save data two different ways depending on whether or not you are viewing the application on the web or using AIR.

Alternate BMP Saving: Using Interfaces

In the source files there is an alternate version of SaveAsBMP.fla located in the Alternate Using Interfaces folder that uses interfaces instead of abstract classes for factory instances' identification and typing. Interfaces are often used when you have no control over the factory instances' superclasses or there existed no common superclass that contained the necessary methods or properties (the interface) necessary for the generated instances to be used in the context needed. Implementing an interface defined with those methods and properties, would ensure to the client application that the necessary methods and properties are understood and exist at runtime.

To the client, the only difference is through how factory-created instances are typed. Instead of using the superclass, the interface represents the type. The alternate example uses the ISave interface.

package {

	import flash.display.BitmapData;
	
	public interface ISave{
		
		function save(bitmapData:BitmapData):void
	}
}

As an interface, the save method is specified but there is no implementation provided. It is the responsibility of the classes implementing an interface to implement the properties and methods an interface contains.

In the client, the factory method changes to return an ISave instance rather than AbstractBitmapSaver, which no longer exists.

function getBitmapSaver():ISave {
	var saver:ISave;
	// etc...
}

The NetBitmapSaver and AIRBitmapSaver classes no longer extend a class (AbstractBitmapSaver) - and don't need to. Instead, all they do is implement ISave and, technically, are free to extend any other class. Implementing ISave requires that they implement the definitions laid out in the ISave definition which, in this case, is simply the method save().

Aside from typing, because a common superclass is not being extended by these classes, they are left to implement what would otherwise be inherited. In the case of AbstractBitmapSaver, the only thing passed down to the NetBitmapSaver and AIRBitmapSaver subclasses that added any functionality was the encode() method. This can easily be made up for in the interface-based versions by using BMPEncoder.encode() directly. In other situations, it could require a lot more work for the class implementing the interface. Here is how the alternate NetBitmapSaver looks:

package {
	
	import flash.display.BitmapData;
	import flash.net.FileReference;
	import flash.net.navigateToURL;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import com.senocular.images.BMPEncoder;
	
	public class NetBitmapSaver implements ISave {
		
		public function NetBitmapSaver(){
		}
		
		public function save(bitmapData:BitmapData):void {
			if (bitmapData == null) return;
			var request:URLRequest = new URLRequest("download.php?filename=image.bmp");
			request.method = URLRequestMethod.POST;
			request.data = BMPEncoder.encode(bitmapData);
			navigateToURL(request);
		}
	}
}

With similar changs made to AIRBitmapSaver, the same functionality for saving a BMP file through flash is provided through the use of interfaces rather than an abstract superclass. The factory method is still being used to delegate the act of saving to subclasses. This alternate approach with interfaces just changes how those subclasses are represented in ActionScript.

Conclusion

There are many different kinds of factories or creational patterns that you can use to create case-specific instances in a similar manner as demonstrated by the SaveAsBMP example. The abstract factory pattern, for example, is a factory for factories. It uses a collection of various factories of which each which create similar types of instances. A factory method is then used to determine which factory is required to create the correct type of instances.

When working with AIR creational patterns like these will become very useful as you create cross-platform desktop applications, especially if you want those applications to also run in the browser. The SaveAsBMP example is a good example of how such an application would need to behave differently based on environment of playback.