17 December 2008
All
This article discusses some of the best practices for developing AIR applications that work well on all supported platforms and operating systems.
AIR applications run on any platform and operating system that support the AIR runtime, including Mac OS X, Linux, and Windows. The AIR runtime lets you concentrate on creating the unique functionality of your application without worrying too much about operating system and platform differences. However, when dealing with those features that interact directly with the operating system, you should consider the best practices discussed in this article to avoid pitfalls that will prevent your application from working everywhere it could.
No two operating systems provide the exact same feature set. Although you can create expressive applications using only those features of the operating system available everywhere, there may be times when you want to take advantage of an OS-specific feature. For example, window menus would seem very out of place to users on Mac OS X. AIR enables you to go beyond the lowest common set of features of the supported operating systems. But, when you use platform-specific features, you should consider the best practices discussed here to make sure that you don't accidentally limit the reach of your applications.
Even the most scrupulous adherence to best practices cannot replace the need for testing. The most important areas to test for cross-platform issues are file system access, window behavior, and networking. Hardware virtualization technology such as VMware or Parallels can reduce the hardware costs of cross-platform testing.
AIR provides several properties for detecting whether the computer on which your application is running supports a particular feature or capability. Wherever possible, you should use these properties rather than detecting the operating system itself.
For example, Mac OS X supports application-style menus that are displayed on the desktop menu bar. When you create a menu for your application, you should use the NativeApplication.supportsMenu property to determine whether application menus are supported.
The AIR properties to use for detecting operating system capabilities are:
NativeApplication.supportsDockIconNativeApplication.supportsMenuNativeApplication.supportsSystemTrayIconNativeWindow.supportsMenuNativeWindow.supportsTransparencyYou should use these properties rather than checking Capabilities.os or using other means to detect a specific operating system. Logic such as that in the following pseudo-code may seem to work, but it can lead to problems on Linux or other operating systems:
if (Windows)
{
// Use system tray
}
else
{
// Use dock
}
There are a few cases where detecting the operating system or platform is desirable. For example, if you are implementing custom chrome in your application, you may want to place the window buttons on the left side of the window on Mac OS X and the right side on Windows and Linux. Adobe plans to extend the reach of AIR to even more platforms in the future, so always make sure that the application behaves reasonably when run on an operating system or platform that you did not expect. This can be as simple as providing a fall-back option when your platform detection code fails to identify a known operating system, as illustrated by the following pseudo-code:
if( Windows ){
//Windows specific code
} else if ( Mac ){
//Mac specific code
} else if ( Linux ){
//Linux specific code
} else {
//Fall-back code
}
AIR does not let you write to the application directory by default, because the directory is not writable to all user accounts on all operating systems. There are ways to get around the AIR restriction, but if you use them, your code will fail on systems on which the operating system itself enforces write protection.
If your application needs to install editable assets, such as a database file, you should copy or move those assets from the application directory to the application storage directory the first time your application is run.
AIR provides several properties for referencing well-known, standard directories on different operating systems. These include:
File.applicationDirectory: The read-only directory in which your application is installed.File.applicationStorageDirectory: A directory for storing external application assets, such as writable files, downloaded images, and so on. This directory can be difficult for a user to locate through the file system, so user documents are better kept in the documents directory.File.desktopDirectory: The desktop folder.File.documentsDirectory: The user's documents folder. Files that a user expects to use outside your application, such as edited pictures or text files, should be stored in a suitable subdirectory of the documents folder.File.userDirectory: The user directory.Each of these properties provides a File object referencing the appropriate directory. Use the resolvePath() method to create or access a file within one of these directories. For example, the following code statement creates a File object referencing a file in the user's documents directory:
File.documentsDirectory.resolvePath("myFile.xyz");
You can also use the File.createTempDirectory() and File.createTempFile() methods to create temporary folders and files. (Note that these files are not automatically cleaned up, so your application should delete them from disk when through with them.)
Although not all file systems are case sensitive, your code will not fail by treating them as if they were. On the other hand, treating a case-sensitive file system as non–case-sensitive can lead to severe bugs.
One way to catch errors in file case when you develop on a case-insensitive file system is to check that the canonicalized name of a File object matches the requested name. For example, the following function returns true if the requested file name matches the canonicalized file name:
public function checkFileCase( path:String ):Boolean
{
var file:File = new File( path );
var requestedName:String = file.name;
file.canonicalize();
if( file.name == requestedName )
{
return true
} else
{
return false;//Case is different
}
}
You could use a function such as this in debug code to detect when your application writes and reads a file using mismatched file name case.
Whenever possible, use URLs rather than native paths. The File class provides both a url and a nativePath property.
Many AIR, JavaScript, HTML, and Flex properties and methods expect a URL string. When setting such properties or parameters, be sure to use the File object url property (or a properly formatted URL string) rather than the nativePath property. If you use the nativePath property of a File object to set a URL, it will be interpreted differently on different platforms. On Windows, a native path used as a URL would be interpreted as an absolute URL. However, on Mac and Linux, a native path used as a URL is indistinguishable from a relative URL. This can lead to situations in which code that seems to work on Windows, fails on Mac OS X and Linux.
AIR provides the File.separator property so that your code can always use the correct separator character when parsing or constructing paths. Use it when you must work with native paths.
File.getRootDirectories will give you an array containing File objects referencing the root directories of the user's computer. On Mac OS X and Linux, the array will contain the single object referencing the root directory ("/"). On Windows, the array will contain an object for each logical drive that is assigned a letter ("C:\", "E:\", and so on). Other platforms, or even a new version of an operating system on one of these platforms, might have a different convention.
AIR provides the File.lineEnding property so that your code can always use the correct line ending character when parsing or writing files.
In some environments, any changes to the size or position of a window, including maximizing or minimizing the window, are completed asynchronously. This means that if you set a property such as the window width in one line of code and read it in the next, then the property will still reflect the old value.
If you need to take some action, such as laying out controls in the window based on the new size or state of the window, you should call this code from the handlers for the related events, such as the native window resize, move, or displayStateChange events or the stage object's resize or fullscreen events.
The native window operations that are asynchronous on some platforms and synchronous on others are:
x, y, width, height and boundsmaximize()minimize()restore()displayStateactivate()Different operating systems, and even different window managers on a single system, impose different rules on native windows based on the type. Utility-type windows in particular are often subject to differing rules about whether they can be maximized and how they can be ordered among other windows. In general, if you use the normal type for standard application windows, the utility type for dialog boxes and tool palettes, and the lightweight type for special purposes or temporary display surfaces (such as tooltips, drop-down lists, and so on), your windows will behave intuitively on all operating systems.
Large transparent windows can consume an inordinate share of the available processor resources. Some platforms perform better than others in this regard. In particular, you should avoid implementing special effects or pseudo-windowing code using an invisible full-screen window.
On Linux, even completely transparent areas block mouse clicks from reaching other windows. Depending on the window manager and user settings, transparency may not be supported at all and transparent areas will be composited against black. You can detect whether transparency is supported using the NativeWindow.supportsTransparency property.
Menus are an area in which user expectations are strongly tied to operating system conventions. When application menus are available (NativeApplication.supportsMenu is true), you should create an application menu conforming to Mac OS X guidelines. When window menus are available (NativeWindow.supportsMenu is true), you should create window menus following Microsoft or Linux conventions (which are very similar to each other).
Guidelines for menu design on the currently supported platforms are available in the following documents:
The following list describes the AIR classes that either implement platform-specific features or that behave differently on different platforms:
NativeApplication.supportsDockIcon to detect whether dock icons are supported.NativeDragUpdate and NativeDragOver events are not dispatched by all operating systems. Since these events can be used to make a UI more responsive, you should use these events where appropriate to add to the user experience, but you must never rely on them.NativeApplication.supportsMenu and NativeWindow.supportsMenu can be used to detect which type of menu is supported.displayStateChanging events cannot be canceled.NativeApplication.supportsSystemTrayIcon to detect whether system tray icons are supported.Developing AIR applications that work on all platforms is generally very easy. However, when you work with the file system and native windows you should avoid making assumptions that apply only to the operating system on which you develop. Likewise, when you implement features specific to a particular operating system, you must make sure that your application is still usable without that feature when run on a different operating system. Following the best practices discussed in this article—and testing—will help give your applications the broadest reach possible with the least effort.