Shipping your Android application: a checklist

I recently completed my first Android application. I learned a lot from the project, which was a ton of fun.

Out of that project popped a checklist of things to do in order to make sure your application is “industrial strength” before it heads out into the Android Market. This isn’t a complete list, but it did grow out of our project backlog as we polished the app and drove towards ship date.

Where possible, I point to a few resources for more information, and other points are good subjects for future blog posts.

Manifest

  • Decide/test what SDK versions you are compatible with, and explicitly set the <uses-sdk> element. Example:
    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="8"/>
    
    • You can set the android:targetSdkVersion attribute to indicate which SDK you built against, and for which the app is intended to run on
    • You can set android:minSdkVersion to indicate the lowest revision of the SDK that the app will run against. Apps built against newer versions of the SDK will generally run on older devices, as long as you are careful not to invoke API calls that do not exist in the older SDK.
    • Here is a reference to the various SDK levels and what releases of Android correspond to them.
    • Here are some references for best practices in targeting your app to run on various versions of Android while still taking advantage of the latest OS features:
  • Decide what screen resolutions you are compatible against. Our app is intended to run on medium and large displays, but does not run well on devices such as the Motorola Charm and Flipout, which have Blackberry-style form factors with small screens. So we included the <supports-screens> element in our manifest, with android:SmallScreens="false".
    <supports-screens android:smallScreens="false"/>
    
  • Is your application intended to run only on a touchscreen, and not on a traditional style (keypad + d-pad) interface?  If so, set the <uses-configuration> elements appropriately. You can specify more than one for an “or”-style grouping of supported configurations. This example says the application is compatible with both stylus-driven (resistive) and finger-driven (capacitance) touchscreens, but one or the other is required:
    <uses-configuration android:reqTouchScreen="stylus"/>
    <uses-configuration android:reqTouchScreen="finger"/>
    
  • Explicitly set a top-level theme for your application, using the android:theme attribute of the <application> element. Here’s an example manifest entry:
        <application android:icon="@drawable/icon"
        			 android:label="@string/app_name"
        			 android:theme="@android:style/Theme.Light"
        			 android:debuggable="false"
        			 android:name="com.myorg.myapp.MyAppSubclass">
            <!-- blah blah blah -->
       </application>
    

Packaging

  • Clean up unnecessary permissions qualifiers in your manifest. You don’t want to ask for more system permissions than your app really needs. People are sensitive to this stuff.
  • Clean up unused assets (image, audio, layouts, etc) in your res folder hierarchy. It’s amazing how assets can get orphaned as your application’s interface moves through iterations of design. Leaving them in the app only increases the size of the APK and takes up more space on your user’s devices.
  • Turn off debug logging:
    • The Android Log API will antagonize the garbage collector if you do string concatenation in your log messages (and we all do that).
  • Shut off debug menus (unless you want your customers to access them).
  • Change the debuggable=true item in your application element to false (as in the above example).

Resources

  • Externalize view layouts from Java code into XML layout files.
  • When defining styles, it helps to explicitly specify all the attributes that you want to be sure are presented a particular way.
    • Different device manufacturers tinker with the built-in themes, and your application can inherit font, size and color attributes different from what you expected when your application is run on a device different from those you used during development.
  • Externalize resources, such as drawables and color definitions, into XML.
  • Externalize strings into XML.
  • Remove hardcoded pixel constants in your code, layouts, and resources. Use device-independent pixels (dp units in your layout files) wherever possible, and more likely than not things will “just work” when your app runs on different screen resolutions.
  • Include assets as needed for high-density, medium-density and low-density displays.

Application

  • Save application state, terminate threads, and clean up state on onPause/onStop/onDestroy handler methods within your activities.
  • Subclass the Android Application object:
    • Register your subclass by setting the android:name attribute of the <application> element in your manifest.
    • Implement application onLowMemory handler, and be a nice citizen.
    • Implement application lifecycle methods onCreate, onTerminate. Create global items to share between activities in onCreate, and clean up resources as best you can in onTerminate.
  • Implement configChanges in your Manifest and onConfigurationChanged in your Activity classes, to intercept default behavior for system configuration changes. If you don’t do this, Android will kill and restart your activities to handle them:
    • When the user rotates the device between portrait and landscape. The following Manifest example will cause Android to invoke onConfigurationChanged in your activity class when the user rotates the device, rather than Android stopping and restarting your activity:
              <activity android:name=".activity.MyAwesomeActivity"
                        android:label="@string/app_name"
                        android:configChanges="orientation"
                        >
                        <!-- remainder of activity declaration goes here -->
              </activity>
      

      And here is how you would respond to it within your Activity:

      	@Override
      	public void onConfigurationChanged(Configuration newConfig) {
      		// TODO do something here
      		super.onConfigurationChanged(newConfig);
      	}
      
    • When the user slides in/out the hardware keyboard on some devices, such as the original Droid/Milestone from Motorola. The following Manifest example will cause Android to invoke onConfigurationChanged when the user changes the keyboard configuration, again without killing your activity:
              <activity android:name=".activity.MyAwesomeActivity"
                        android:label="@string/app_name"
                        android:configChanges="keyboard|keyboardHidden"
                        >
                        <!-- remainder of activity declaration goes here -->
              </activity>
      
    • In both cases, it’s worth noting that you’re intended to respond to the change within the body of onConfigurationChanged. But you can also not bother to override onConfigurationChanged, which effectively disables Android’s activity kill/restart mechanism. You should do this only if you are certain your activity will not be affected by the change in question.
    • More information about configuration changes is available at the official Android developer site.
  • Implement Activity onLowMemory handlers.
  • Make sure background threads are terminated properly on switch between Activities.

External Storage

  • Support application installation to SD card if at all possible. Owners of devices such as the Nexus One, with limited internal storage, will thank you.
  • Properly determine location if you plan to use the SD card for temporary storage.
    • In Froyo (Android 2.2) and later, use the Context#getExternalFilesDir method to get a directory for your temp files, which will automatically be cleaned up by the Android OS if the user uninstalls your application.
    • On earlier releases of Android, you can create a folder in the same place that getExternalFilesDir would provide. When the user updates to Froyo, Android will still auto-remove the contents on app uninstall.
private static final String EXT_STORAGE_ROOT = "/Android/data/com.mycompany.myapp/files/";
cacheDir = new File(android.os.Environment.getExternalStorageDirectory(), EXT_STORAGE_ROOT + dirName);

Networking

  • Ensure your application behaves properly (posts error message, etc) when Airplane mode is enabled.

Battery Life

Most of these suggestions came from the Google I/O presentation Coding for Life — Battery Life, That Is:

  • Try to avoid EDGE/2G for large data transfers – use 3G or WiFi if you can.
  • Use GZIP compression for HTTP access whenever possible:
    • The less time you keep the radio active, the bigger you win with battery life.
    • The CPU used for decompression is fast, native, and negligible compared to keeping the radio running.
  • Reduce garbage collection:
    • Retain a single instance to an object, perhaps as a class member, rather than reallocating it.
    • Set references to null when you are done with them, to release them to the garbage collector earlier rather than later.
  • Recycle Regex Matcher objects:
    • Matcher#reset(newString) to reuse the matcher object.
  • Recycle StringBuilder object instances:
    • use StringBuilder#setLength(0) – good as new!
  • Share static objects (such as StringBuilders) in classes such as ListAdapter, where they get called often.
  • Use stream-based parsers for JSON and XML, not tree-based ones.
  • Keep background services alive only as long as necessary, and run them only as frequently as necessary.
  • Periodic background services should wake up on an alarm, try to run only when the system is already running, and schedule themselves to be invoked “inexactly”, as a group along with other alarms.
  • Fixed-point arithmetic is better than floating-point. ARM processors don’t have native floating-point instructions and must do all floating point as multi-instruction algorithms, which is really expensive.
  • Be careful with wake-locks. They can kill the battery like a vampire.
    • Use a window-managed wake-lock if you can, which Android will automatically handle for you:
    • getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
      
    • Or set the android:keepScreenOn="true" attribute in your XML layouts for controls that must keep the screen alive.
    • Finally, if you must create and manage your own wake-lock, specify a timeout so it will expire if you forget to release it.
Posted in Android | Tagged , | Leave a comment

Opening links in AIR’s HTML loader

The AIR HTMLLoader class and its control wrapper, the <mx:HTML> component, are a pretty straightforward way to display web content within an AIR application.

While the combination of HTMLLoader and ActionScript is pretty powerful, HTMLLoader and <mx:HTML> do pose some limitations if you are trying to develop a serious web application in AIR. But one of these limitations can be worked around with a little code.

Out of the box, HTMLLoader is not able to handle links within HTML documents which open in a new window (by setting target="_blank" in your link tag, for instance).  If you click on the link, and the navigateInSystemBrowser property in the HTMLLoader object is set to false, then absolutely nothing happens.

Sönke Rohde offers an innovative solution based on modifying the document after it has been loaded, trapping every instance of target="_blank" and rewriting the node to call a custom JavaScript function that then opens the link in the system browser. The net effect is that only links that open external windows appear in the system browser; all other links continue to open in-place. (Changing the navigateInSystemBrowser property affects all links).

My application, and others I would guess, does not find opening new documents in the default system browser desirable, but yet we want to handle these “open in new window” clicks.  Since my application is single-window, I’d simply like to redirect these links to open in the current browser.

Doing so is relatively straightforward, but not spelled out in detail, so I thought I’d post the solution here for the benefit of all you copy/paste coders in the crowd (of which I consider myself a proud, card-carrying member).

First, you need to define an subclass of the AIR HTMLHost class.  This class provides a series hook functions to allow you to customize various browser events invoked by JavaScript code, including Window.open() among them.

package com.mycorp
{
	import flash.html.HTMLHost;
	import flash.html.HTMLWindowCreateOptions;
	import flash.html.HTMLLoader;

	public class MyHTMLHost extends HTMLHost
	{
		public function MyHTMLHost(defaultBehaviors:Boolean=true)
		{
			super(defaultBehaviors);
		}

		override public function createWindow(windowCreateOptions:HTMLWindowCreateOptions):HTMLLoader
		{
			// all JS calls and HREFs to open a new window should use the existing window
			return htmlLoader;
		}
	}
}

The createWindow function is the magic bit here.  The createWindow method is tasked with returning an HTMLLoader responsible for opening the requested link.  Normally, following Adobe’s docs, you’d create a new HTMLLoader, attach it to a newly created NativeWindow, and return the HTMLLoader instance.

In our case, we want to load links in the existing HTMLLoader, we just override createWindow and return the existing HTMLLoader.  (Don’t worry about where the htmlLoader property is set; that’s handled for you automatically by the next bit of code).

After you’ve defined this class, you then need to instantiate it and assign it to the htmlHost property on the HTMLLoader object.  The HTMLLoader will then use the instance of your custom HTMLHost, and as a bonus will assign its htmlLoader property for you.

private function onCreationComplete():void
{
    // ... other stuff ...
    htmlLoader.htmlHost = new MyHTMLHost();
}

Next, run your application and visit a document with a target="blank" link:

<p>Click <a href="http://www.google.com" target="_blank">here</a> to open a document in a new window.</p>

And the link should create a new document in the current HTML browser component. Success!

Posted in AIR | 3 Comments

Functional testing AIR applications with FlexMonkey, Part 4

In the first three parts of this series, we built our AIR application for use with FlexMonkey and authored tests.

In this part, we examine how to get your FlexMonkey AIR test suite running as part of your continuous build.

  • Part 4 (this article) will integrate the application into your build using Ant.
  • Part 5 (next article) provides an alternative to Ant scripting, with a Ruby-based test automation server you can integrate into your build.

Gorilla Logic, the makers of FlexMonkey, does not document this process, though this is more of a reflection of its bleeding-edge nature than it is neglect on their part. And although they do provide supporting code, those parts are not included in the standard FlexMonkey download. To get access, you will need to check out the source from the FlexMonkey project repository at the Google Code website.

Finally, I had to modify a few of FlexMonkey’s ActionScript classes, and use the Ant task in creative ways, to make this process work.  The modified classes are included in the download accompanying this article.

So hang on while the Dump Truck encounters some chuckholes in the road, and do keep away from anything spilling over the sides.

Meet ElectroWeb

To demonstrate the techniques illustrated in this article, and to give you a ready-to-run example, I’ve created a toy AIR application called ElectroWeb. ElectroWeb is a minimal web browser application, developed using AIR’s <mx:HTML> component. It contains just enough functionality to make it testable with FlexMonkey.

ElectroWeb screenshot

You can download the source code for ElectroWeb at the end of this article.

Architecture

As in Part 2, we build a special version of your AIR application that gets run as part of test phase of your build. This time around, we bundle even more FlexMonkey code libraries into your application to support the build environment.

Automated Builds in Context

The first time we built your application for use with the FlexMonkey console, it ended up looking like the following:

FlexMonkey Test Authoring Architecture

FlexMonkey Test Authoring Architecture

Where we are going

Your AIR application, built to run as part of a continuous build scenario, will look like this:

FlexMonkey Continuous Build Architecture

FlexMonkey Continuous Build Architecture

The first thing to notice is that the FlexMonkey Console has now been replaced by a Build Automation System. The FlexMonkey console is great for authoring test cases, and running them interactively. But as an interactive application, it doesn’t have much place in the tool chain for an automated system. The Build Automation system is represented here as an Ant task, or (in part 5 of this series) a Ruby script.

The Build Automation System talks over a network socket to a new piece of the puzzle: an embedded Monkey Test Launcher that is linked into your AIR application.  The test launcher dynamically loads a separate .swf file containing the ActionScript TestSuite, TestCase, and Test classes generated by the FlexMonkey console that comprise your functional test suite. Then the embedded test launcher runs the tests.

When the tests are complete, the test launcher sends back the results, in XML format, to the Build Automation System.  The automation system writes the results to a log file, then registers success or failure status for the build as a whole.

A little checklist

So, here’s the checklist to follow for build system integration:

  1. Generate ActionScript source for your test suite classes.
  2. Build a Monkey AIR test launcher module, including your test suite classes, as a separate .swf file.
  3. Include the modified FlexMonkey test launcher classes, along with the FlexMonkey libraries, as part of the files you link with your AIR application.
  4. Use the FlexMonkey Ant plugin to drive the tests, using the monkey-test-launcher task.
  5. Do the necessary Ant scripting to integrate this plugin into your build process.
  6. Finally, make sure your build/CI server has the necessary Flash runtime, Flex SDK, AIR SDK, Adobe Automation Libraries, and FlexMonkey SDK libraries installed (not covered in this article).

Ready? Then let’s press forward.

Building your test suite classes

Generating AS3 classes from the FlexMonkey Console

To begin, you must turn your existing FlexMonkey project into executable ActionScript. From the FlexMonkey Console, open your project.  Then choose File > Generate AS3 from the menu.

The FlexMonkey "Generate AS3" menu option.

Generating ActionScript for test cases from within FlexMonkey

Compiling Test Suite Classes into a SWF Module

To compile the test cases into a SWF (dark green box in our diagram), we create a small MXML module (not a component or application) that loads the test suite classes.  The module is dynamically loaded by the embedded test launcher when the application runs:

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" implements="net.digitalprimates.fluint.modules.ITestSuiteModule">
   <mx:Script>
   <![CDATA[
		import testSuites.ElectroWebTestSuite.*;
 		public function getTestSuites() : Array
		{
			var suiteArray : Array = new Array();
			suiteArray.push(new ElectroWebTestSuite());
			return suiteArray;
		}
   ]]>
   </mx:Script>
</mx:Module>

Here’s a sample Ant target to build the test suite into a SWF module. See the download for the full build.xml file:

<target name="compile_test_module" depends="init">
    <java jar="${MXMLC.JAR}" fork="true" failonerror="true">
		<jvmarg value="-Xmx512m"/> <!-- avoid running out of heap space during compile -->
		<arg value="-debug=${DEBUG}"/>
        <arg value="+flexlib=${SDK_HOME}/frameworks"/>
        <arg value="-file-specs=${MONKEY_TEST_MODULE_SOURCE_FILE}"/>
        <arg value="+configname=air"/>
		<arg value="-include-libraries+=${LIB_ROOT}/AirMonkeyLibrary.swc"/>
		<arg value="-include-libraries+=${LIB_ROOT}/MonkeyFlexUnitLibrary.swc"/>
		<arg value="-include-libraries+=${LIB_ROOT}/FlexAutomationLibrary.swc"/>
		<arg value="-include-libraries+=${LIB_ROOT}/fluint.swc"/>
		<arg value="-include-libraries+=${AUTO_LIB_ROOT}/automation_agent.swc"/>
		<arg value="-output=${DEPLOY_DIR}/${TEST_MODULE_NAME}.swf"/>
	</java>
</target>

Modifying the FlexMonkey Test Launcher

To make the Test Launcher work, I had to modify some of the source code provided by Gorilla Logic. In particular, module loading did not work out of the box, and so I had to adapt my own solution from the pieces provided.

FlexMonkey Classes Modified for Test Launcher SWF

The Monkey Test Launcher (yellow, at the end of the lightning bolt in our diagram) depends upon the following two classes.

Class: com.gorillalogic.monkeylink.MonkeyLinkTestLauncher

This class was part of the MonkeyLinkTestLauncherLibrary project in the FlexMonkey code repository.

It was specially modified to do the following:

  • Uses an alternate class – com.gorillalogic.monkeylink.TestModuleLoader – to wrap the whole process of dynamically loading test suite classes.
  • Adds some extra error handling code, with some trace statements to help troubleshoot problems in the adl debugger.

Class: com.gorillalogic.monkeylink.TestModuleLoader

This class was part of the MonkeyFluintAirTestRunner project in the FlexMonkey code repository. It is nearly identical to the original, except that a dependency on a logger object has been removed, and replaced with simple trace statements.

Including the Test Launcher Classes

You don’t need to do anything special to build these classes. Just make sure you have included the source code for both .as files in your project, and then include those classes in the build for your AIR application.

You do not need to explicitly include com.gorillalogic.monkeylink.TestModuleLoader. The com.gorillalogic.monkeylink.MonkeyLinkTestLauncher class imports it, and so it is transitively included.

The sample Ant target in the next section has the appropriate includes arguments to pull in the MonkeyLinkTestLauncher class.

Building your application

Here’s a sample Ant target for building the application:

<target name="compile_functional_test" depends="compile_test_module">
    <java jar="${MXMLC.JAR}" fork="true" failonerror="true">
		<jvmarg value="-Xmx512m"/> <!-- avoid running out of heap space during compile -->
        <arg value="-debug=${DEBUG}"/>
        <arg value="+flexlib=${SDK_HOME}/frameworks"/>
        <arg value="+configname=air"/>
		<arg value="-source-path"/>
		<arg value="${TEST_ROOT}"/>
        <arg value="-file-specs=${MAIN_SOURCE_FILE}"/>
		<arg value="-includes"/>
		<arg value="com.gorillalogic.monkeylink.MonkeyLinkTestLauncher"/>
		<arg value="-include-libraries+=${LIB_ROOT}/AirMonkeyLibrary.swc"/>
		<arg value="-include-libraries+=${LIB_ROOT}/MonkeyFlexUnitLibrary.swc"/>
		<arg value="-include-libraries+=${LIB_ROOT}/FlexAutomationLibrary.swc"/>
		<arg value="-include-libraries+=${LIB_ROOT}/fluint.swc"/>
		<arg value="-include-libraries+=${AUTO_LIB_ROOT}/automation.swc"/>
		<arg value="-include-libraries+=${AUTO_LIB_ROOT}/automation_agent.swc"/>
		<arg value="-include-libraries+=${AUTO_LIB_ROOT}/automation_dmv.swc"/>
		<arg value="-include-libraries+=${AUTO_LIB_ROOT}/datavisualization.swc"/>
		<arg value="-output=${DEPLOY_DIR}/${APP_NAME}.swf"/>
    </java>
    <copy file="${APP_DESCRIPTOR}" todir="${DEPLOY_DIR}"/>
</target>

Running Your Tests From the Build

Once you have your test suite module and the instrumented application build, the last step is to run your application with a special Ant task called monkey-test-launcher.

The monkey-test-launcher task does the following things:

  • Launches your application.
  • Starts a test server that is internal to the Ant process.
  • The test server connects over a socket to the test launcher embedded in your AIR application.
  • The test server tells the test launcher to run the tests, then waits for the tests to finish.
  • The test server captures the XML output of the tests and writes it to log files.

What About the monkey-test-runner task?

The FlexMonkey Ant tasks contain another task, monkey-test-runner, which is not to be confused with monkey-test-launcher.

The monkey-test-runner task is for another purpose: it runs a special AIR test runner application that drives FlexMonkey test suites for non-AIR applications. Your AIR application takes the role of FlexMonkey’s AIR test runner. In other words, monkey-test-runner can be safely ignored.

Configuring the Ant Task

The creative bit in making the monkey-test-launcher task do its thing is the way in which you launch your AIR application. The monkey-test-launcher task takes the following arguments:

  • launcher – The test runner application. Normally, this is a path to an application, linked with the FlexMonkey libraries, which then loads the SWF to test. In this case, we provide a command line which runs our AIR application in debug mode using AIR SDK’s adl utility. This is the “secret sauce” for launching our app from the Ant task.
  • targetSwf – Normally the .SWF file for the application under test, but since our AIR application is both the test runner app, and the app under test, this value is unnecessary for our use case. The value provided in the example below is only a placeholder, but it is pretty much ignored.
  • targetSwf – The loadable .SWF module containing the test suite we generated earlier.
  • snapshotDir – The folder holding snapshot files for FlexMonkey verification steps.
  • toDir – The folder where test output should be written.

Here is a sample Ant target definition to run the monkey-test-launcher task:

<target name="run_functional_test" depends="compile_functional_test">
	 <monkey-test-launcher
          timeout="0"
          launcher="${ADL} ${DEPLOY_DIR}/${APP_NAME}-app.xml"
          targetSwf="${DEPLOY_DIR}/${APP_NAME}.swf"
          testModuleSwf="file://${basedir}/${DEPLOY_DIR}/${TEST_MODULE_NAME}.swf"
	      snapshotDir="${TEST_ROOT}/snapshots"
          toDir="${REPORT_DIR}"
          haltonfailure="false" />
</target>

Running the Ant Task

At this point, you should be able to run:

ant run_functional_test

and have Ant build the application, the test module, and test launcher, execute the application, connect to the embedded test runner, run the tests, and save the output. You are done! Time for slushies.

Notes

You may find it useful to create three Ant targets that each build one of the three variants of your AIR app:

  1. A Distributable Release version. This target generates the releasable .air file that you provide to your end users.
  2. A Test Authoring version (linked as in part 2 of this series). This target is run on a one-off basis outside of the normal build procedure, in order to author tests.
  3. A Continuous Integration version, built and run during the test phase of your continuous build as described in this article.

The build.xml file for the ElectroWeb application contains Ant targets for generating each of these build variants.

What now?

At this point you’ve built your AIR application with FlexMonkey, created a test suite, and even run the tests as part of your build.  What more could you want?  A way to do this without Java and Ant, perhaps?  We have you covered in the next and final article in the series.  Stay tuned.

Downloads

Click here to download the sample code (test module, test runner cases and Ant script) as a .zip file. This is a fully buildable and runnable application, but you will need to visit the build.xml file and set the FLEX_SDK property to a value appropriate for your environment.

Posted in AIR, Test driven | 6 Comments

Adobe Flex and AIR error codes

This post is an attempt to gather in one place links to all of the official Adobe pages containing error codes for ActionScript, Flash, Flex and AIR.

I will update this post with more links to error pages, as I find them.  If you know of one not on this list, please drop me a note in the comments with the link.

Credit to Mike Chamber’s blog for getting me started.

Posted in AIR, Flex | Leave a comment

Launching AIR apps from the command line on the Macintosh

The process for launching an AIR application from the command line seems straightforward enough – Oliver Goldman from Adobe has a blog entry describing how it is done. Of course, it would be great if things worked as described, but they don’t.

If you follow his instructions, and you are on the Mac, you’ll get the following error as a reward for your effort:

michael-portuesis-macbook-pro: /Applications/testweb.app/Contents/MacOS/testweb
2010-01-22 09:55:51.572 testweb[6998:613] NSDocumentController Info.plist warning: The values of CFBundleTypeRole entries must be 'Editor', 'Viewer', 'None', or 'Shell'.

Special, huh? It looks like AIR (1.5.3, and at this writing 2.0beta2 as well) has a bug that produces an invalid Info.plist file on the Macintosh.  Specifically, it fails to include a CFBundleTypeRole entry for the document type representing the application itself.

There is a workaround. You can launch the app using the Mac OS ‘open’ utility:

machine: ~user$ cd path to app bundle
machine: ~user$ open -a app_name

But then you can’t pass command line arguments to your app. Which is a major bummer if you are trying to use your AIR app in an automated, scripting-driven scenario. Which is the primary use case for command-line invocation of your AIR app.

So, here’s another workaround.  It’s only applicable for a specific installation, meaning you can’t fix the .air file itself, but instead you hack the installed application bundle.  Nevertheless, I felt it worth documenting here.

You need to manually edit the Info.plist entry generated by Adobe’s adt packaging tool. Using the Finder, maneuver to your Applications folder, and find the application you installed. Right-click, and select Show Package Contents to drill down inside the application bundle:

From here, navigate inside the ‘Contents’ folder and double-click on Info.plist. The Mac Property List Editor opens.

From here, select View > Show Raw Keys/Values. Open CFBundleDocumentTypes, and right-click on the document type item representing your AIR application – Item 1 (the only item) in this example.

Now select Add Row. Choose the key CFBundleTypeRole then the appropriate value for your application – Editor, Viewer, None, or Shell. For my application, I chose None.

Save and quit the Property List Editor, then try launching your AIR app from the command line again. The annoying warning message should now be gone.

You’re welcome.

Posted in AIR, Rants | 3 Comments

RSpec: Stubbing RAILS_ENV and other constants

RSpec is a great tool not only for writing tests in Ruby, but helping you articulate the requirements for your project in a clean and executable fashion.

Every now and then, the need arises to stub the value of a Ruby constant in your RSpec examples. The most obvious use case is to test functionality in your Rails app that is environment-specific, for instance a Rails action that should never be run in production.

While Googling around for some code examples showing me how to do this, I came across this article at Stack Overflow, complete with example code! Unfortunately, the example wasn’t suitable for use in a real production test suite. It had several problems:

  1. The example works by defining new constants to replace the originals using Object.const_set. But after it executes your code block, it simply deletes those constants. It should be smarter, by saving the old values, and restoring them after the code block is complete.
  2. It prints out the Ruby warnings which ensue whenever you try to redefine a constant within the confines of an instance method.
  3. Finally, if the code block throws an exception, the original example never cleans up after itself. The redefined constants stick around, which can have bad effects for whatever code executes outside the block.

Here’s an updated version of the code sample that fixes these problems:

def with_constants(constants, &block)
  saved_constants = {}
  constants.each do |constant, val|
    saved_constants[ constant ] = Object.const_get( constant )
    Kernel::silence_warnings { Object.const_set( constant, val ) }
  end

  begin
    block.call
  ensure
    constants.each do |constant, val|
      Kernel::silence_warnings { Object.const_set( constant, saved_constants[ constant ] ) }
    end
  end
end

And here’s a short example of it in action:

    it "does not allow links to be added in production environment" do
      with_constants :RAILS_ENV => 'production' do
        get :add, @nonexistent_link.url
        response.should_not be_success
      end
    end
Posted in Ruby, Test driven | 3 Comments

Renewing AIR certificates

I’ve spent some time understanding the AIR certificates used to sign your application for distribution.

The AIR documentation goes to great pains to outline the situations when you need to update your application’s certificate:

In some circumstances, you may need to change the certificate you use to sign your AIR application. Such circumstances include:

  • Upgrading from a self-signed certificate to a certificate issued by a certification authority
  • Changing from a self-signed certificate that is about to expire to another
  • Changing from one commercial certificate to another, for example, when your corporate identity changes

In my case, I was only moving from an old certificate to a renewal of the same certificate, and not changing anything else.

The docs point point out that renewing a certificate is not the same as changing a certificate. Most importantly, the path to local application storage is not the same after you change certificates, forcing you to either migrate the data between folders or else lose whatever persistent data was stored there. The docs then explicitly call out what to do if you change certificates:

To change certificates:

  1. Create an update to your application
  2. Package and sign the update AIR file with the new certificate
  3. Sign the AIR file again with the original certificate (using the ADT -migrate command)

It wasn’t clear to me that you also follow the same procedure to move to a newer version of your existing certificate. After a bit of trial and error, I found out that the procedure is identical in both cases.  Sign the old (expiring) certificate with -migrate, and the new version with -package:

adt -package -storetype pkcs12 -keystore cert/newcert.p12 bin/myapp.air bin/myapp-app.xml -C bin myapp.swf
adt -migrate -storetype pkcs12 -keystore cert/oldcert.p12 bin/myapp.air bin/myapp.air

This will make the auto-update mechanism upgrade your application without skipping a beat, or losing access to locally stored data.

For more information on renewing digital certificates, see these two posts on Oliver Goldman’s Pursuit of Simplicity blog:

Posted in AIR | Leave a comment

More Flash, Flex and Ruby E-Zines

In search of ever more content to clog up my Kindle, I’ve come across the following two e-zines worth checking out.  Both are free downloads in PDF format.

  • The Rubyist feels a little bit like the Pragmatic Programmer e-zine, but focused entirely on the world of Ruby. It has the same kind of smorgasboard of people profiles, architectural/philosophical screeds, and nuts-and-bolts coding articles. It’s a good slice of Ruby culture – whether that’s a good or bad thing is up to you.
  • Flash and Flex Developer’s Magazine comes off a little like Doctor Dobbs, or a trade periodical like .NET Developer’s Journal. It has too much coverage of things like commercial tools and trade shows I don’t care for.
    Editing and proofreading is uneven, and some of the articles are definitely written by folks for whom English is a second language. But it does have a lot of introductory articles, and each is headed with a bullet-point summary of what you will learn, and what you need to know in order to understand the article.
    It’s worth breezing through each issue to look for interesting and useful stuff, though the formatting (lots of columns of tiny type) is more suited for paper printouts than for Kindle browsing.
    They have a holiday gift of all their issues to date as one free download.

If you have any more suggestions for E-Zines of a technical nature, be sure to let us know in the comments!

Posted in Flex, Recommendations, Ruby | Leave a comment

Functional testing AIR applications with FlexMonkey, Part 3

In previous posts in this series, we looked at the best overall approach for AIR applications with FlexMonkey, as well as building your AIR application and getting it to run with the FlexMonkey Console.

In this part, we present tips and tricks for authoring tests with the FlexMonkey Console.  Although you can put together some nice test suites with FlexMonkey, the Console is a little bare-bones, with only rudimentary means of editing test cases. The biggest limitations are:

  • No cut/copy/paste to allow you to edit your test sequence.
  • No way to reuse test sequences in other test cases.
  • No setup and teardown actions for your tests.

Here’s some workarounds:

Hand-Editing the XML

Because the FlexMonkey Console does not support cut/copy/paste, sometimes it is easier to load the XML file into a text editor to make some changes.  This, by far, is the biggest technique for working around limitations in FlexMonkey console.

FlexMonkey saves test suites in .xml files, for example:

src/myProjectTestSuites.xml

The FlexMonkey console uses two XML files.  One is named something similar to xxxxxProject.xml, and the other is named something like xxxxxSuites.xml.  (Replace the xxxxx with the name that’s specific for your application).

The xxxxxProject.xml file contains all the settings you choose in the FlexMonkey console. The xxxxxSuites.xml file contains an XML description of your test cases.  The FlexMonkey console opens and saves this file as you edit your tests in the console. But you can also open that file in a text editor such as Textmate or Notepad, and make changes to it.

Once you make the changes, save the file, reload it into FlexMonkey, and choose File > Generate AS3… from the menu to update the ActionScript source files for your test cases.

To avoid confusion, and lost data, I always take care to make sure that only either my text editor or the FlexMonkey Console are active at once – not both.

Scripting the HTML Browser

If your AIR application uses the HTML browser component, be warned that you cannot script interactions with pages displayed within the web browser. For example, you cannot script form interactions or clicks on page links using FlexMonkey.

But you can provide a location bar text field as part of your application, together with some application logic to wire it into the HTML control.  FlexMonkey can type addresses into the location bar, and the browser will jump to the page you specify.

Similarly, you can wire up other browser functions, such as Back and Forward, to controls and use FlexMonkey to poke at them.

Making your controls visible

User interface controls must be visible on the display in order for FlexMonkey to interact with them.  If they are hidden or not visible, you will get an error when the test is run.

Creating special controls for functional testing

Consider adding some controls to your user interface, specifically intended to support functional tests.  They can be hidden for normal user operation, but activated during functional test.

Here’s an example: I added a “Clear” button to my user interface, to erase an associated text field. My FlexMonkey tests use this rather than clicking in the field and manually erasing the contents.  Not only does this take fewer steps in the test script, it helps make your scripts order-independent. Text selection operations can be fooled if the previous action filled the field with a long string.

Another example: FlexMonkey cannot test secondary windows within AIR applications, including the standard File dialog.  This is another limitation of the Adobe Automation API.  But you can work around the problem by rolling your own ‘file  dialog’, that is used only for test purposes.  It doesn’t need to be fancy like the real one – just a popup with a text field and a confirm button.  Your FlexMonkey scripts type a path into the field and click the button, rather than using the real file dialog.  Then, your application posts this ‘file dialog’ as a popup from the main application window.

My AIR application accepts a command-line option, -ftest, which causes it to open in non-fullscreen mode, display the hidden FlexMonkey user interface controls, and skip the AIR update version check. None of these things are suitable for actual production use, but do make sense in a functional test scenario.

Alternatively, since your AIR application is specially built for FlexMonkey testing, you could implement some type of compile-time feature to add support for FlexMonkey-specific controls.

You do need to exercise some judgment and restraint – if you put in too many FlexMonkey-specific controls, you can easily end up testing functionality that no one sees, and miss testing actual use case scenarios.  But as a way of working around FlexMonkey’s shortcomings, I think it’s worth it to put in some ‘artificial’ functionality if it allows better coverage of real use cases.

Ordering test cases

In an automated build environment, the test runner built into the AIR application (see the next article in our series) does not execute tests within a test case in the same order that you have defined them in the FlexMonkey Console.  It runs them in ascending alphebetical order, by name of test.

For that reason, each test in your test suite should not depend upon state set up by the test immediately preceding.  And at the end of test cases, you should ensure that they get the application back to a known state in preparation for the next test case that might run.

Although it can be seen as a virtue that tests should be order-independent, you can force the order in which test cases and individual tests run by giving them alphabetical names.  A test named AAAxxxxxx will execute first in your sequence, and one called ZZZxxxxxx will execute last (assuming your other names have farily normal names).

Setup and teardown actions

The FlexMonkey IDE does not appear to support “setup” and “teardown” actions that automatically get executed before and after each Test within a TestCase (though the underlying Fluint unit test framework does).

You can also take advantage of the alphabetical ordering of tests to provide setup and teardown for your test cases.  I name the setup test in each test case AAAxxxxxx, and the teardown test ZZZxxxxxx.  They provide setup work (such as login and logout) for the tests in between.

I have not yet investigated using the underlying FlexMonkey API to implement setup and teardown actions via ActionScript.  One approach might be to subclass the generated testcase classes to add setup/teardown, and add the subclassed test cases to your test suite.  This technique would keep the test cases pure for IDE code generation.

Documenting your test cases

Use the “description” field of the Flexmonkey’s Verify command to state the expectation of the test in human terms.  This is important, as FlexMonkey doesn’t offer much in the way of documenting your test cases, and all the tests in your suite start to look identical when presented as a set of steps in the Console or tags in an XML file.

Verifying contents of text fields

You can also use the text displayed in a text field as part of a Verify assertion.  But Verify assertions can’t take conditionals like “or”, and they can only match literal strings – not patterns.  Keep this in mind to help avoid brittle tests that work fine when run in one environment (for instance, the FlexMonkey console) but not another (from your Continuous Integration system).

Adjusting delays and think time

Sometimes you will need to play with the lengths of delays between actions within a script, to allow time for processing within your application.  For instance, my AIR application incorporates a web browser component, and so therefore needs to wait for the browser component to load the next page.

Removing unwanted snapshots

The Snapshot feature when creating a Verify command takes a screen snap of the component you are verifying, whether you are comparing bitmaps in your assertion or not.

I’m not sure if there is a way to work around this, but you can hand-edit the snapshotURL field in the XML, set verifyBitmap="false" and remove the path to the snapshot file. Then you can delete the snapshot file entirely.

Now that you have built your AIR application, run it under FlexMonkey, and have authored some tests, the next thing is to automate the tests and make them run as part of your Continuous Integration system.  We’ll cover that in the next article, part 4 in the series.

Posted in AIR, Flex, Test driven | 5 Comments

Functional testing AIR applications with FlexMonkey, Part 2

In Part 1 of this series, we examined the best overall approach for testing your AIR application with FlexMonkey. In this article, we take a look at building an instrumented version of your AIR application and getting it working with the FlexMonkey IDE Console.

A note about versions

I originally did this work using FlexMonkey 1.0 B2.  I’ve tested and updated this walkthrough for FlexMonkey 1.0 RC1, the most recent version.

At this writing, FlexMonkey 1.0 RC1 is currently incompatible with Flex SDK 3.4 or later.  So for best results, use Flex 3.2 or 3.3 and version 3.2 of the Adobe Automation Libraries (see below).

Architecture

Here’s a picture of where we’re going:

FlexMonkey Test Authoring Architecture

The large green box to the right is your AIR application.  Normally your application is comprised of the ActionScript classes that you write, including any MXML components you declare (in green), linked together with Adobe’s Flex and AIR SDKs (in aqua), and any third-party .swc files you may need to round out your application’s feature set (in gray).

To this list we add some additional swc files.  They break down into two categories:

  • Adobe’s Automation Libraries – (orange) These provide the bridge between FlexMonkey and the Flex user interface controls in your code.  They allow user interface controls to be driven programmatically without user interaction.
  • FlexMonkey Libraries – (yellow) This includes the Flunit test runner, as well as the FlexMonkey classes necessary to communicate with the FlexMonkey IDE.

The FlexMonkey Console, running as an AIR application in a separate process, communicates via a network socket with the embedded MonkeyLink libraries within your application.  You interact with the FlexMonkey Console to author tests, while your AIR application obeys FlexMonkey, dangling like a puppet on strings.

Libraries To Include

The following table lists the libraries you will need to to build your application against, and where to locate them.

  • The Flex Automation API download can be found at Adobe’s Flex SDK download page.  Make sure to get both the “Adobe Flex Automation Libraries for Flex Builder” and the “Adobe Flex Data Visualization Components for Flex Builder”.
    • Copy the contents of the libs folder into your Flex SDK, underneath the folder flex_sdk_dir/frameworks/libs.
    • Copy the corresponding locale files underneath flex_sdk_dir/frameworks/locale.
    • If you are using a Mac, do not copy the files with the Finder! Instead, copy them from Terminal.  The Finder will erase all existing files in the destination folder and replace them with the files you are copying – which is not what you want.  Instead, you want to copy these files in addition to what is already there.
    • NOTE: You will also need a Flex Builder Professional license from Adobe ($$$) in order to use these libraries. Without a license, they will work but will stop processing after a fixed, limited number of automation requests.  The limit is not enough to run any kind of a real test suite for a production software product.
  • The MonkeyAccessories download can be had from the Gorilla Logic download page.
    • Also, if you haven’t done so already, install the FlexMonkey Console from this page by clicking on the download badge resembling a TV screen.
NameWhat It's ForWhere to Find ItWhere to Put It
AirMonkeyLibrary.swcSupport for testing AIR applications with FlexMonkeyMonkeyAccessories. Look in the 'MonkeyLibs' folder'libs' folder of your AIR project
MonkeyFlexUnitLibrary.swcIntegrates FlexMonkey with a unit test frameworkMonkeyAccessories. Look in the 'MonkeyLibs' folder'libs' folder of your AIR project
FlexAutomationLibrary.swcIntegrates FlexMonkey with the Adobe Automation APIMonkeyAccessories. Unpack the 'Easy2BuildMonkeyLink.zip' file, then look in the 'libs' folder'libs' folder of your AIR project
Easy2BuildMonkeyLink.swcAllows FlexMonkey Console to connect with the portions of FlexMonkey embedded into your applicationMonkeyAccessories download. Unpack the 'Easy2BuildMonkeyLink.zip' file, then look in the 'bin' folder.'libs' folder of your AIR project
fluint.swcUnit Test FrameworkMonkeyAccessories. Look in the 'MonkeyLibs' folder'libs' folder of your AIR project
automation.swcAdobe's Automation APIAdobe's Flex Automation API downloadin 'flex_sdk_dir/frameworks/libs' folder. (Unpack the Adobe automation_sdk download into your Flex SDK folder).
automation_agent.swcAdobe's Automation APIAdobe's Flex Automation API download'flex_sdk_dir/frameworks/libs' folder.
automation_dmv.swcAdobe's Automation APIAdobe's Flex Automation API download'flex_sdk_dir/frameworks/libs' folder.
datavisualization.swcAdobe's Data Visualization API (which probably should not be needed, but somehow got included as a compile-time dependency)Adobe's Flex Automation API download'flex_sdk_dir/frameworks/libs' folder.

Building the application

Sample Command Line

Here’s a sample command line for building an AIR application with these libraries. (Though formatted with multiple lines here, this would be a single line to your shell interpreter).

amxmlc -locale en_US -output build/browser.swf -services source/services-config.xml
-library-path+=lib/applicationupdater_ui.swc
-include-libraries+=lib/Easy2BuildMonkeyLink.swc
-include-libraries+=lib/AirMonkeyLibrary.swc
-include-libraries+=lib/MonkeyFlexUnitLibrary.swc
-include-libraries+=lib/FlexAutomationLibrary.swc
-include-libraries+=lib/fluint.swc
-include-libraries+=/Users/portuesi/flex_sdk/frameworks/libs/automation.swc
-include-libraries+=/Users/portuesi/flex_sdk/frameworks/libs/automation_agent.swc
-include-libraries+=/Users/portuesi/flex_sdk/frameworks/libs/automation_dmv.swc
-include-libraries+=/Users/portuesi/flex_sdk/frameworks/libs/datavisualization.swc
-- source/browser.mxml

The first two lines and the final line will be different for your application, and of course the path names for each of the .swc files will be different for your build environment.

Things to Note

Don’t ship or deploy this special version of your application to customers! The extra FlexMonkey and Automation libraries extract a toll in terms of download size, as well as memory and CPU resources on the user’s system when they run your application.

Running the application

To run the application with the FlexMonkey console, follow these steps.

1. Run the application in debug mode from the command line, using the adl tool found in the Adobe AIR SDK.  Here is an example command line:

adl build/browser.xml

The .xml file in this example is the AIR application descriptor XML file that all AIR applications require.

2. Next, start the FlexMonkey console application and create a project for your AIR application.

The FlexMonkey console will automatically connect to your application.  You can tell this from the FlexMonkey console toolbar.  The rightmost icon, next to the “disc” (save) icon will appear green.

FlexMonkey console 'connected' icon

3. You are now ready to author and play back tests within FlexMonkey console.

To learn more about the FlexMonkey console, consult the documentation provided by Gorilla Logic.  Also stay tuned for the next article in this series, where we examine ways to use the FlexMonkey console to effectively author tests.

Posted in AIR, Flex, Test driven | 15 Comments