AS3 tricks 1: swfs in AIR

Suppose you want to make an AIR application but you have a history of working in actionscript, not mxml. Do you need to learn an entire new language to make AIR applications? No, not really. Flex has a SWFLoader class made just for you, and if you work in fullscreen interactives like me you can let Flex handle screen management and just tell your SWFLoader to fill the stage.

Getting started: In Flex Builder 3, select File > New > Flex Project. Create a desktop application and give it a project name. Something clever, like "Testing". We'll delete it later and you can do this again with your real project. Click "Finish" and you're taken to your mxml.

So now what? There's not a lot there. But click the little bug at the top anyhow. Voila, you're looking at your AIR application preview. It opens as a separate application, so you can close it by either quitting (File > Close, [ctrl]-Q, [apple]-Q, whatever) or by switching back to Flex and clicking the red stop square in the lower right. Do one of these and we'll start filling in some of the mxml.

What is mx:, in particular mx:WindowedApplication? Ok, flex is not actionscript. mx is markup describing flex packages; in this case, WindowedApplication is part of the mx core package. If you're really interested in flex markup you can browse through the Flex 3 language reference or try using the built in help. I recommend the online reference; it's up to date and sometimes has very useful user comments. So the help says that it's an "application container." Pretty accurate description; what we want is a container to hold our swf. But in order to make this holder do what we want we'll need to figure out how to set properties of the container.

In actionscript, you would instantiate something (var myThing:Thing = new Thing()) and then start applying properties to it (myThing.name = "myThing"; myThing.color = 0xFF0000;, etc.). In mxml properties are set in the declaration and are separated with commas. Here's an example from a current project of mine:

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute" showFlexChrome="false" paddingBottom="0" paddingLeft="0"
    paddingRight="0" paddingTop="0" width="640" height="480"
    applicationComplete="init()" backgroundColor="0x000000" focusRect="false">

If you copy and paste this into your application where it currently has the meager <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> it will still run but it'll look a little different. There's a little magic about to happen, too... when it makes the window it's trying to call a function called "init()". So now we need to create this function and give it something to do...

Put the following code inside your WindowedApplication; ie, between <mx:WindowedApplication> and </mx:WindowedApplication>:

  <mx:Script>
    <![CDATA[
      private function init():void {
        stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
      }
    ]]>
  </mx:Script>

Starting to look a little more like actionscript now, isn't it? This will put us in fullscreen mode, black background; go ahead and run it again. Hit [esc] to go back to windowed mode. Let's add a SWFLoader; that will hold our swf.

  <mx:Script>
    <![CDATA[
      import mx.controls.SWFLoader;

      private var initTimer:Timer = new Timer(10);
      private var loader:SWFLoader = new SWFLoader();
     
      private function init():void {
        stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
        loader.addEventListener(Event.COMPLETE, onSwfLoaded);
        loader.addEventListener(IOErrorEvent.IO_ERROR, swfLoadError);
        loader.maintainAspectRatio = false;
        loader.load("Main.swf");
        addChild(loader);
        initTimer.addEventListener(TimerEvent.TIMER, swfResize);
      }
     
      private function onSwfLoaded(evt:Event):void {
      trace ("swf loaded successfully!");
      initTimer.start();
      }
     
      private function swfResize(evt:TimerEvent):void {
      trace ("resizing swf");
        initTimer.stop();
        loader.percentHeight = 100;
        loader.percentWidth = 100;
      }
     
      private function swfLoadError(evt:IOErrorEvent):void {
      trace ("oh no! file not found!");
      }
    ]]>
  </mx:Script>

Ok, almost there... You can run it again. Here's the chain of events that's happening: mx.WindowedApplication is telling init() to run. init() is telling loader to load a swf named "Main.swf". Since that file doesn't exist (if you've been following these instructions) you should see "oh no! file not found!" in your trace window. Go ahead and quit the AIR preview and we'll make the swf.

Finally, a swf! Last thing to do is to get that Main.swf into your AIR application. Select File > New > Actionscript Class. Enter a name ("Main") with a Superclass of Sprite. You'll get a warning that using the default package is discouraged but that's ok; this is your main application swf and kind of belongs in the default package. You can put Main's subclasses in packages.

Change your function Main to look like this (so we'll have something on the stage):

  public function Main()
  {
    super();
    this.graphics.beginFill(0x336633, 1);
    this.graphics.drawRoundRect(0, 0, 200, 100, 15, 15);
    this.graphics.endFill();
  }

Now we tell Flex to compile this swf when you debug or publish the application. This one is a little obscure but it makes development so much easier. Navigate to Project > Properties, select "Flex Modules" on the left, and click "Add" on the right. Browse to your Main.as file (it's in "src" when you click browse); flex will fill in the generated filename as Main.swf. Click OK and we're ready to debug again. Go ahead, click the bug!

SWF in AIR: You should be looking at a nice green rectangle on a black background. The chain of events now went more like this: WindowedApplication fires off init(), init() tells loader to load "Main.swf". When the file finishes loading a timer starts and you get a trace saying "swf loaded successfully!". The timer is necessary because if you tell a SWFLoader to resize before it renders (ie immediately when it finishes loading) it will not properly resize. A little annoying, but nothing a timer can't fix. Something I like about SWFLoaders is the percentHeight and percentWidth properties; if you grab the lower right corner of your application window and resize it you can see that the swf scales to fit the current AIR window. Events get fired off when that happens but I'll leave handling of that (and changing aspect ratios) for another day.

Clean up after yourself! Last thing to do is to delete these files and start over again with an application of your own. Close Testing.mxml and Main.as in Flex Builder, right-click/[ctrl]-click the project folder in Flex Navigator, and select "Delete". You can either let Flex Builder delete the files or just delete the reference to the files in Flex Navigator; your call.