Prerequisite knowledge

This article assumes that you are
comfortable with ActionScript 3, and that
you have a pretty thorough understanding
of general Flash concepts such as sprites
and bitmaps. Additionally, I highly
recommend that you read my previous
article entitled Authoring mobile Flash
content for multiple screen sizes in order to
familiarize yourself with some of the basics
of multiscreen application development
you will recognize many aspects of this
with the Flash Platform.
Required products

User level


Additional Requirements

Adobe Flex SDK
Sample files
The sample code for this project is all open source and hosted on GitHub at the links below. You can either check out the source code using git, or you can simply download the zip or tar files provided on the project pages.
I recently wrote a post on my blog entitled One Application, Five Screens which showed a single AIR application called iReverse running on OS X, Windows 7, Ubuntu, an iPhone, an iPad, a Motorola Droid, and in the browser. A few days later, I also showed iReverse running on a Nexus One while demoing how easy the AIR tool chain is to use with Android. Although the iPhone and iPad aren't as relevant as they once were, Adobe AIR either reaches or will reach most other next-generation devices, so knowing how to author across devices and screen sizes is becoming increasingly important. This article describes the techniques I used to write a single application that automatically adapts to any screen size and any resolution, and runs just about everywhere.
There are a several different techniques for developing multiscreen applications. A few that I considered are:
  1. Architect your application in such a way that device-specific presentation layers can be placed on top of your core application logic.
  2. Develop components and libraries that can be reused in as many environments as possible.
  3. Develop a single code base that can automatically adapt to any environment.
I think the first two options are what most developers will find work best for them. Rather than writing a single application that gets deployed everywhere, the goal would be to reuse as much code as possible, and therefore minimize the amount of device-specific code that needs to be written. For example, if you were developing a Twitter client, you might reuse your Twitter protocol library, persistence layer, and perhaps most of your application logic, but customize your presentation layer for each target platform or device.
Although I think this model-view-controller approach will make sense for most applications, I decided to try the third option described above. I wanted to see if I could write a single application—one single code base—that ran on every supported platform and device, completely unchanged. As it turns out, with a little practice, it's more feasible than I expected, and I suspect many developers will find that it works for their projects, as well.

What this project isn't

Now that I've explained what this project is all about, I want to take a minute to explain what it isn't. After publishing the One Application, Five Screens video, I received a lot of comments from developers who didn't understand what I was doing. Aside from the "Java has been able to do this since 1995" comments, the second biggest misconception was that I was simply using if statements or #ifdef preprocessor directives. In my mind, that's cheating. There isn't a single if statement in all the 1,500 or so lines of code in this project that checks which platform, device, or OS the application is running on. Rather than adapting to specific devices, iReverse adapts to the context or environment. In other words, rather than checking to see if a device is a Motorola Droid or a Windows 7 computer, it pays attention to things like screen resolution and pixels per inch. In fact, I actually went a step further. iReverse doesn't even care about things like orientation or whether the screen is big or small; it simply does its best to lay itself out as optimally as possible no matter what the constrains are. That means iReverse will work fine on the smallest screen we current support to the biggest (soon to be flat-panel televisions).

Project architecture

Before I drill down into specific implementation details, it probably makes sense to start with an overview of the general architecture I used.
Although iReverse is one single code base, I still have separate projects for each device it runs on. Currently that includes the following projects:
The Reversi project (named generically in case I decide to change the name of the application) basically contains all the code for the game while the device-specific projects all contain nothing more than simple wrappers which include the Reversi project in their source paths (to set a project's source path in Flash Builder, go to Properties > Flex Build Path). The Reversi project contains roughly 1,500 lines of code while the ReversiAndroid project consists entire of the following:
package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; [SWF(frameRate="24")] public class ReversiAndroid extends Sprite { private var reversi:Reversi; public function ReversiAndroid() { super(); this.stage.scaleMode = StageScaleMode.NO_SCALE; this.stage.align = StageAlign.TOP_LEFT; this.reversi = new Reversi(-1); this.addChild(this.reversi); } } }
As you can see, the job of the wrappers is to:
  • Specify a frame rate using SWF metadata.
  • Instantiate the Reversi class.
  • Configure the stage.
  • Add the Reversi instance to the stage.
That's pretty much it. So if all the iReverse wrappers basically do the same thing, why have separate projects for each? There are several reasons:
  • The frame rate can be customized. I haven't run into an circumstance yet where I had to use a different frame rate from one device to another, but as we continue to support additional devices, it might be a useful option.
  • PPI, or pixels per inch, can be customized. The -1 argument being passed into the Reversi class constructor is a pixels per inch value. When -1 is passed in, the Capabilities.screenDPI property is used to determine PPI, but since the API is hard-coded in some environments (where it might be difficult to get a device's pixel density), it is sometimes useful to be able to override it.
  • Separate application descriptors. Application descriptors let you customize things like initial window size (for testing on the desktop), screen orientation, icons, and device-specific metadata. Below is the application descriptor file for ReversiAndroid:
<?xml version="1.0" encoding="utf-8" standalone="no"?> <application xmlns=""> <id>com.christiancantrell.reversiandroid</id> <supportedProfiles>desktop mobileDevice</supportedProfiles> <filename>iReverse</filename> <name>iReverse</name> <version>0.9</version> <description>A one or two player Reversi game.</description> <initialWindow> <autoOrients>true</autoOrients> <fullScreen>true</fullScreen> <aspectRatio>portrait</aspectRatio> <content>[This value will be overwritten by Flash Builder in the output app.xml]</content> <title>iReversi</title> <systemChrome>none</systemChrome> <transparent>false</transparent> <visible>true</visible> <width>480</width> <height>854</height> </initialWindow> <icon> <image57x57>assets/Icon.jpg</image57x57> </icon> </application>
Note: In the example above, I have the screen size set to 480×854 which is the resolution of a Motorola Droid. Specifying the resolution of the Droid allows me to "simulate" one of my target devices during the development process, but it won't affect the dimensions of the application on the device itself. In other words, when running on a Nexus One (which has a 400×800 screen), the application will run at the appropriate size.
Figure 1 shoes my Flash Builder project panel showing the architecture described in this section.
The architecture described in this section
Figure 1. The architecture described in this section.

Setting up the Reversi projects

Below are instructions on how to set up the Reversi projects. Note that setting up the Reversi projects is not required. If you would rather just learn the general concepts discussed in this article, feel free to skip this step.
  1. Make sure you have Flash Builder 4 and the AIR 2 SDK installed. If you don't have Flash Builder 4 yet, Flex Builder 3 should work. For instructions on overlaying the AIR 2 SDK over the Flex SDK in Flash Builder, see the AIR 2 beta 2 release notes.
  2. Download the iReverse projects that you want to set up. You will need the Reversi project, and whichever other projects you are interested in (links above). For the sake of this article, I will assume that you are setting up the ReversiDesktop project.
  3. In Flash Builder, create a new ActionScript project called Reversi and point it to the directory containing the Reversi project you just downloaded. This should be the directory containing the src directory (but not the src directory itself).
  4. Add the airglobal.swc from the AIR SDK to the Reversi project. Go to Properties > ActionScript Build Path, then click the Library Path tab. Click the Add SWC button, then navigate to the airglobal.swc in your AIR SDK.
  5. Create a new Flex Desktop project called ReversiDesktop and point it to the directory containing the ReversiDesktop project you previously downloaded. Be sure to use a Flex SDK with the AIR 2 SDK overlaid on top of it.
  6. Right-click the file, and choose Set as Default Application. You can then right-click the auto-generated ReversiDesktop.mxml file and delete it.
  7. Add the Reversi project to the ReversiDesktop project's source path. In the ReversiDesktop project, go to Properties > Flex Build Path, and click the Source path tab. Click Add Folder and navigate to the src directory inside of the Reversi project that you downloaded previously. Click OK to save your changes.
ReversiDesktop should compile and run just fine. Follow the same process (starting from step 5 above) for any of the other Reversi project that you want to set up. They all work identically except for ReversiBrowser. To set up the ReversiBrowser project, follow these steps:
  1. Download the ReversiBrowser project.
  2. Create a new ActionScript project called ReversiBrowser and point it to the directory containing the ReversiBrowser project you just downloaded.
  3. Follow the instructions in step 7 above to add the Reversi project source code to the ReversiBrowser project.
  4. Make sure you have Flash Player 10.1 (or newer) installed.
  5. If you don't have it already, you will need the playerglobal.swc for Flash Player 10.1. If it isn't already there, place it in the directory of whichever Flex SDK you are using in the frameworks/libs/player/10.1 directory (you may have to create the 10.1 directory yourself).
  6. Go to Projects > ActionScript Compiler, and under Adobe Flash Player options, select "Use a specific version." If the value is 10.0.0 or below, change it to 10.1.0.
The ReversiBrowser project should compile now, and running it should open iReversi in your default browser.
Now that you have iReverse up and running, let's explore some of the specific techniques it uses to run across multiple screen sizes.

Handling orientation changes

iReverse supports three distinct orientation modes:
  • Portrait: the height of the application is greater than its width.
  • Landscape: The width of the application is greater than its height.
  • Flat: The game is in two-player mode, and the device is lying flat.
Although iReverse is very orientation conscious, it has a strange way of handling orientation events: it doesn't. Although it responds perfectly when a device is rotate , and although AIR makes it very easy to listen for and respond to orientation change events ( StageOrientationEvent.ORIENTATION_CHANGE ), iReverse doesn't use any orientation-related APIs. Instead, iReverse uses the more generic and more multiscreen friendly technique of listening for stage resize events ( Event.RESIZE ).
The primary advantage to using stage resize events rather than orientation change events is that your application will lay itself out properly in any context—whether on a device that is being rotated, or inside a native window that's being resized, or inside a plugin container of arbitrary dimensions in a browser.
For this technique to work, the Reversi class first needs to know when it has been added to the display list. Only after it has been added to the display list is it safe to access the stage property. The code below is how iReverse knows when its stage property won't be null:
this.addEventListener(Event.ADDED, onAddedToDisplayList);
The code below is an abbreviated version of the onAddedToDisplayList function:
private function onAddedToDisplayList(e:Event):void { this.removeEventListener(Event.ADDED, onAddedToDisplayList); this.stage.addEventListener(Event.RESIZE, doLayout); }
Now whenever the stage is resized, the doLayout function gets called. There are three circumstances that will cause the an Event.RESIZE event to fire:
  1. The application initializes.
  2. The window (if the application has one) is resized.
  3. The device's orientation changes (if applicable).
As it turns out, this is exactly what we need. We need the application to lay itself out when it initializes, whenever its window is resized (in environments like the desktop that have windows), and when the device's orientation changes (when running on a mobile device or tablet).
Now that we know how to hook into the right event to cover all your layout scenarios, the question is what the layout logic should look like. Of course, this will be heavily dependent on the specific application, but here are some general pointers:
Remove all children
One of the first things you will want to do is remove all the children from the stage like this:
while (this.numChildren > 0) this.removeChildAt(0);
Draw a background
iReverse uses the following code to draw a solid color background:
var bg:Sprite = new Sprite();;, 0, this.stage.stageWidth, this.stage.stageHeight);; this.addChild(bg);
Since iReverse uses a solid color background, it would also work to simply set the background color of the SWF using metadata like this:
However, I prefer drawing my background in code for the following reasons:
  • I can change it programmatically (which means I could allow the user to customize the background color).
  • I can keep all my color values stored in application constants like BACKGROUND_COLOR .
  • I can easily replace the solid color with a gradient, image, or a pattern.
Figure out your orientation
Although iReverse doesn't use orientation change events, it does need to know what the device's orientation is in order to know how to lay out the game. To make this quick and easy, I wrote the following function:
private function getOrientation():String { return (this.stage.stageHeight > this.stage.stageWidth) ? PORTRAIT : LANDSCAPE; }
There are other ways to find out a device's orientation, but this is the most device-independent way of doing it. In other words, this even works on devices that don't have a concept of orientation (like the desktop) which means as the native window is resized, the application will be laid out appropriately.
Draw the application
Most of the drawing of the iReverse user interface happens inside of the following conditional:
if (this.flat) // Head-to-head { // The game is in two player mode, and the device is flat. // Orient the scores toward each player. } else if (this.getOrientation() == PORTRAIT) // Portrait { // Position the scores at the top, and the game buttons at the bottom. } else // Landscape { // Position the scores on either end, and place the buttons in the corners. }



iReversi in portrait mode
Figure 2. iReversi in portrait mode
iReversi in landscape mode
Figure 3. iReversi in landscape mode
iReversi flat mode
Figure 4. iReversi flat mode

Using APIs that aren't supported everywhere

Although iReverse primarily uses stage resize events ( Event.RESIZE ) to know when to redraw, it also listens for accelerometer update events ( AccelerometerEvent.UPDATE ) in order to determine when to go into or come out of "head-to-head" or "flat" mode. Of course, not all the devices that iReverse runs on have accelerometers, and not all AIR application profiles support the accelerometer APIs (for instance, the desktop profile doesn't support the Accelerometer class since relatively few computers have accelerometers at this point). So how can one code base cover devices with and without accelerometers?
Fortunately, the Flash Platform makes this extremely simple by "stubbing out" APIs in contexts even where they aren't supported. In other words, even in the desktop profile where accelerometer events aren't supported, the Accelerometer class still exists which means my code compiles, verifies, and runs just fine.
I do make one small concession, however. The code below is what I use to configure an Accelerometer instance and listen for accelerometer update events:
if (Accelerometer.isSupported) { this.accelerometer = new Accelerometer(); this.accelerometer.setRequestedUpdateInterval(1500); this.accelerometer.addEventListener(AccelerometerEvent.UPDATE, onAccelerometerUpdated); }
As you can see, I use the isSupported property on the Accelerometer class to check to see if the accelerometer APIs are supported before using them. This actually isn't strictly required; I could remove that if statement and unconditionally register for accelerometer update events, and the code would compile, verify, and run just fine, however in this case, I prefer to be explicit about APIs that don't work everywhere. In my opinion, it improves the readability of the code.

Input events

Since the primary way of interacting with an application on the desktop or in the web browser is with a mouse and keyboard, applications usually capture corresponding mouse and keyboard events. Since the primary way of interacting with a mobile application is with your fingers, mobile applications usually capture corresponding touch events. So how do applications designed to run in both places handle input events?
Touch events only work in environments where touch is supported by the host system's hardware and software. The same is not true of mouse events. Mouse events work on the desktop, in the browser, and on mobile devices. Therefore, if your application needs to work across screens, you are usually better of just capturing mouse events and not capturing touch events at all. The exception to this rule is when you need touch-specific data or functionality such as multitouch, gestures, or properties of the TouchEvent class such as the touchPointID . But if all you need to know is when something is tapped or clicked on, mouse events will work everywhere.
iReverse only uses mouse events and no touch events. But it also registers for keyboard events so users can cycle through the history of the game using the left and right arrows. The code below registers for keyboard events:
this.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
The code below is responsible for reacting to the left and right arrow keys:
private function onKeyDown(e:KeyboardEvent):void { switch (e.keyCode) { case Keyboard.RIGHT: this.onNext(); break; case Keyboard.LEFT: this.onBack(); break; } }
Notice how none of the keyboard event code is concerned with whether the host device even has a keyboard or not. Once again, the Flash Platform insulates developers from the differences between platforms. On devices that have keyboards, keyboard events are thrown. On devices without keyboards, no keyboard events are thrown. As long as your application doesn't depend on functionality that may not be available everywhere, this is a great way to give your application additional functionality in environments that support it. For instance, the left and right arrow keys obviously don't work on the Nexus One since it doesn't have a keyboard, but they work great on the Droid using the D-pad. Additionally, on touch-enabled Windows 7 computers, forward and back gestures (swipping right or left across the screen) result in left and right arrow keyboard events, and therefore can be used to navigate through a game's history.

Bitmaps v. vectors

Flash works great with both vectors and bitmaps. Designing and styling your application with bitmaps is often a great technique since it lets designers work with tools and assets they are accustomed to, and because bitmaps perform better when used with transitions and tweens. But the problem with bitmaps is that they don't always scale down well, and they almost never scale up with much fidelity at all. That means if your application is going to be usable on screens of all different sizes, embedding or loading bitmaps might not be the best approach.
Vector graphics behave very different. Since vector shapes are computed from algorithms, they scale well across huge ranges of screen sizes which makes them ideal for multiscreen applications. The problem with vectors is that they can be more resource intensive if not managed properly.
With iReverse, I took a middle-of-the-road approach. All the graphics in the game (stones, the game board, button skins, etc.) are generated from vectors so that they can scale to any screen size, but then they are also converted to bitmaps in order to ensure that the application performs well. I use two different techniques to go from vectors to bitmaps.
All DisplayObjects (including Sprites which is what all the iReverse visual elements inherit from) have a property called cacheAsBitmap . When set to "true", the vector is transformed from a vector to an in-memory bitmap which means that it will perform better in the context of visual effects such as transitions and tweens. As with almost all performance-related best practices, however, sometimes cacheAsBitmap can do more harm than good. For instance, I don't use cacheAsBitmap for the stones in the game since applying an alpha tween (which is the effect I use to show stones being captured) to a DisplayObject with cacheAsBitmap set to true will actually dramatically decrease performance since the object is re-cached each time the alpha property changes. If I were moving an object using its x , y , or z properties, however, setting cacheAsBitmap to true would dramatically improve performance.
All the stones in iReverse are bitmaps by the time the user sees them, but they actually start out as vectors. Whenever the size of the stage changes, the following process occurs:
  1. The new size of the stones is calculated based on the size of the stage. Specifically, I figure out the size of the board (the length of the shortest dimension of the stage), then divide by eight (since there are eight columns and rows in reversi).
  2. Two "temporary" Sprites are created (one for black, and one for white), circles are drawn in them using ActionScript drawing APIs, and a filter is applied to give them a slight three-dimensional look.
  3. The BitmapData.draw() function is used to draw the two Sprites into BitmapData objects.
  4. The BitmapData objects are used to create two new Bitmap objects which are stored as class-level variables.
  5. New instances of the Bitmap objects are created for every stone that needs to placed on the board.
Below is the code that turns the stone vectors into bitmaps:
var cellSize:uint = (this.board.width / 8); var stoneSize:uint = cellSize - 4; var tmpStone:Sprite = new Sprite();;, stoneSize/2, stoneSize/2);; tmpStone.filters = [this.stoneBevel]; var tmpStoneBitmapData:BitmapData = new BitmapData(tmpStone.width, tmpStone.height, true, 0x000000); tmpStoneBitmapData.draw(tmpStone); this.whiteStoneBitmap = new Bitmap(tmpStoneBitmapData);
The technique described above gives me all the advantages of vectors (i.e. they can be scaled to virtually any size without any loss in quality), and all the performance advantages of bitmaps (a high frame rate can be maintained during alpha tweens).
Data persistence
Most applications require some sort of data persistence. Even the simplest of games should save the current state of the application whenever it changes in case the application gets closed for any reason.
Adobe AIR provides four primary mechanisms for persisting data:
  • File system. The AIR file APIs can be used to save the state of an application to the file system.
  • SQL APIs. SQLite is embedded inside of the AIR runtime which means applications can use the databases to store, organize, and retrieve information extremely effectively.
  • EncryptedLocalStore . The EncryptedLocalStore is an extremely secure way to store and retrieve sensitive data.
  • SharedObjects . Shared objects (in this context, they are usually referred to as "local shared objects") provide a simple and fast way to store and retrieve serialized data.
When considering which technique to use, there are several things to consider:
  • How secure does the data need to be? If it needs to be secure, consider using EncryptedLocalStore or an encrypted SQLite database.
  • How much data do you need to store? Large amounts of data are better stored in SQLite tables or as files.
  • How organized do you need the data to be? Large amounts of complex relational data should almost always be stored in SQLite tables. Less complex data like the state of a game can easily be stored in flat files or in local shared objects.
  • What environments will your application run in? The browser plugin doesn't contain the SQLite APIs, and the EncryptedLocalStore isn't supported in the mobile AIR runtimes, so where you want your application to run will almost certainly affect your approach to data persistence.
Since I wanted iReverse to run everywhere (desktop, browser, phones, tablets, and eventually other devices like televisions and set-top boxes), and because the data iReverse needs to save is relatively simple, I chose the one technique that is guaranteed to work everywhere: local shared objects.
iReverse saves the following information:
  • The entire history of the game (so players can navigate backward and forward move by move).
  • The game mode (single or two-player).
  • Whose turn it currently is.
  • Which color the computer is playing (if the user is playing a single player game).
All the information listed above is saved at the end of every move so that the game can be shut down at any time for any reason, and the state can be fully recovered. And since I use local shared objects to store the data, it works identically on all devices.
Below is the code that saves the state of the game:
private function saveHistory():void { ++this.historyIndex; var historyEntry:HistoryEntry = new HistoryEntry(); historyEntry.board = this.deepCopyStoneArray(this.stones); historyEntry.turn = this.turn; this.history[this.historyIndex] = historyEntry; for (var i:uint = this.historyIndex + 1; i < 64; ++i) { this.history[i] = null; }[HISTORY_KEY] = this.history;[PLAYER_MODE_KEY] = this.playerMode;[COMPUTER_COLOR_KEY] = this.computerColor;; }
The persistence paradigm I chose is primarily geared toward mobile since mobile applications can be closed at any time, and therefore the state of the application should be saved as soon as possible after it changes. For instance, some mobile platforms close applications when you switch away from them, and some might close an application at any time because memory is running low. Additionally, the game might get paused (or closed) when receiving a phone call, or when responding to a text message, or a phone's battery could always suddenly go dead. Although the approach to persistence I chose is most suitable to mobile platforms, I was surprised by how well it worked on the desktop and in the browser, as well. Although applications don't usually get shut down unpredictably on the desktop, it's still nice that your game is always saved in case you accidentally quit or need to reboot after installing something in the background. And in the browser, it makes a huge amount of sense in case you accidentally navigate away from the page. As soon as you go back, your game is just as you left it, and the entire game history is preserved.
It's not uncommon for paradigms that make sense in one context to become conventions and start being used in other contexts, as well. For example, the concept of "back" and "forward" buttons was really pioneered by browsers, but you now find many apps (including the Finder on Mac and Windows Explorer on Windows) that use the same concept. I believe that conventions and paradigms that we typically associate with mobile devices (persistence, multitouch, and so on) will become standard on other platforms, as well. Using the Flash Platform to develop these multiscreen experiences will allow your apps to easily port these concepts across devices.

Writing the computer opponent

One of the things I had to consider in designing the computer player was CPU usage. I considered an approach where the computer would calculate all possible outcomes several moves in advance, but it doesn't take a math genius to figure out that the number of possible outcomes to a game—especially in its early stages—is tremendous. Although I was confident that I would be able to write something that would run fairly well on the desktop, I wasn't sure how well it would "degrade" on slower mobile processors.
Since the point of writing iReverse wasn't to build an advanced AI, I decided on a very simple approach that I knew would run well on any processor. The computer simply tries to make the most strategic moves first, and considers less and less strategic moves as necessary. Since the logic isn't recursive or computationally intense, the computer is able to choose its next move in a very short amount of time on every device I've used for testing. In fact, I eventually put a one-second delay in because when I started doing user testing (having friends and family play against the AI to test it out), I found that the computer moved so quickly that some opponents found it insulting. After spending several seconds or even minutes choosing a move, some players weren't happy when the computer instantly captured large numbers of their stones.
When writing applications designed to run on a range of devices from a single 400MHz processor to multiple quad-core processors, make sure your application will scale up or down computationally as well as visually. Here are some techniques you might consider using:
  • Keep computationally intense logic separate from the rest of the code so that it can be customized per device. For instance, I could have written a ComputerPlayer interface which I passed into the Reversi class constructor. That would have enabled me to write an advanced implementation for the desktop, and something simpler and faster for lower powered devices.
  • Give players options. If I had had more time to spend on the AI, I would have probably tried to come up with a simple mode and a more advanced mode, and given players the ability to choose which one they wanted. I think if users explicitly choose a more advanced AI option, they would be more willing to wait for it to make its next move.
  • Do heavy processing on a server. For very processor-intensive tasks, consider doing them on a server rather than on devices which might not be well-suited (in other words, many devices might have more bandwidth to spare these days than CPU cycles). For example, Google's voice recognition technology on mobile devices actually does the processing on a server rather than on the client, as does the Dragon Dictation iPhone application.

Where to go from here

If you haven't done so already, I highly recommend that you read my previous article entitled Authoring mobile Flash content for multiple screen sizes in order to familiarize yourself with some of the basics of multiscreen application development with the Flash Platform. Between that article and this one, you will learn everything you need to know about writing applications that adapt to any screen size and can run everywhere the Flash Platform is supported.