I recently had the pleasure of trying to force Adobes braindead AIR framework to update a Flex application to a new version that was using Flex 4.5 instead of 4.1. This was a major pain in the ass. Here I want to describe the steps I took to finally make it work.

Old App vs. New App

This was the Application descriptor for the old version:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<application xmlns="http://ns.adobe.com/air/application/2.0">
  <id>...</id>
  <filename>...</filename>
  <name>...</name>
  <version>3.0.7</version>
  ...
</application>

This was the Application descriptor for the new version:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<application xmlns="http://ns.adobe.com/air/application/2.6">
  <id>...</id>
  <filename>...</filename>
  <name>...</name>
  <versionNumber>4</versionNumber>
  ...
</application>

Flex 4.1 Apps that use Air require Air 2.0 as a minimum, Flex 4.5 Apps require Air 2.6.

The Air Updater Framework

In the application descriptors above you see that the version tag has changed to versionNumber. This change was introduced in Air 2.5. At the same time, Adobe introduced a similar change to the update descriptors. These are two versions of the same Air update, once for Air < 2.5, once for Air >= 2.5:

<?xml version="1.0" encoding="utf-8"?> 
<update xmlns="http://ns.adobe.com/air/framework/update/description/2.0"> 
  <version>@version@</version> 
  <url>http://.../system/@air_name@</url> 
  <description>@update_message@</description> 
</update>

<?xml version="1.0" encoding="utf-8"?> 
<update xmlns="http://ns.adobe.com/air/framework/update/description/2.5"> 
  <versionNumber>@version@</versionNumber> 
  <url>http://.../system/@air_name@</url> 
  <description>@update_message@</description> 
</update>

The Air update process works like this:

  1. The Air updater reads the update descriptor from a server
  2. If it detects a version increase, it downloads the new version
  3. It unpacks the new version and verifies the application descriptor
  4. It installs the new version and restarts the app

The problem is step 3: The Updater from Air 2.0 can’t process the Air 2.6 application descriptor and throws an Error #16824 (Version mismatch).

The solution (theory)

Adobe provides a solution in the knowledge base that is confusing and can be translated to simpler terms:

  1. Create an intermediary version that is built with Air < 2.5, but compile it with the Application Updater libraries from Air >= 2.5
  2. Distribute that version to your clients using a Air 2.0 update.xml
  3. Create the new version with Air >= 2.5
  4. Distribute the new version using a Air 2.0 update.xml
  5. From then on you should use the 2.5 namespace for the update.xml

Step 4 is crucial:

Although the intermediary version was built with the new update framework that can process the 2.5 update.xml, doing so will result in an error.

Air will complain that the update descriptor namespace must match the application descriptor namespace (of the currently running application, not the one you just downloaded).

Now, all this is quite frustrating but it sounds pretty clear to implement, until you actually start with step 1.

The solution (practice)

This isn’t actually all that hard with Flash Builder:

Go to “Project Properties”, “Build Path”, unfold the Flex Framework in the list and remove applicationupdater.swc and applcationupdater_ui.swc. Then click “Add SWC” and add those two files from your Flex 4.5 framework location (/Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/libs/air/applicationupdater.swc on Mac).

The catch? All but the most trivial of applications will use the commandline compiler in a custom build script or the mxmlc task in an Ant buildfile. How do you perform the equivalent of what we just did in Flash Builder in those cases?

This crap took me half a day to figure out. I’m giving you the Ant version:

Before, my compile task in my build.xml looked like this:

<target name="compile" depends="css, copy_assets">
    <echo>Compiling to ${OUTPUT}</echo>
    <mxmlc file="${MAIN_SOURCE_FILE}" output="${OUTPUT}/${FLEX_APP_NAME}.swf" keep="${MXMLC.KEEP}" headless-server="true" warnings="false">

        <load-config filename="${FLEX_HOME}/frameworks/air-config.xml"/>

        <compiler.source-path     path-element="${APP_ROOT}/src"/>
        <compiler.source-path     path-element="${APP_ROOT}/locale/{locale}"/>

        <compiler.library-path dir="${APP_ROOT}/libs/" append="true">
            <include name="PureMVC_AS3_2_0_4/bin/PureMVC_AS3_2_0_4.swc"/>
            <include name="purePDF_0.74/bin/purePDF.swc"/>
        </compiler.library-path>
    </mxmlc>
</target>

I had to change it to this:

<target name="compile" depends="css, copy_assets">
    <echo>Compiling to ${OUTPUT}</echo>
    <mxmlc file="${MAIN_SOURCE_FILE}" output="${OUTPUT}/${FLEX_APP_NAME}.swf" keep="${MXMLC.KEEP}" headless-server="true" warnings="false">

        <load-config filename="${FLEX_HOME}/frameworks/air-config.xml"/>

        <compiler.source-path     path-element="${APP_ROOT}/src"/>
        <compiler.source-path     path-element="${APP_ROOT}/locale/{locale}"/>

        <compiler.library-path dir="${FLEX_HOME}" append="false">
            <include name="libs"/>
            <include name="locale/{locale}"/>
        </compiler.library-path>
        <compiler.library-path dir="/" append="true">
            <include name="Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/libs/air/applicationupdater_ui.swc"/>
            <include name="Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/libs/air/applicationupdater.swc"/>
        </compiler.library-path>
        <compiler.library-path dir="${FLEX_HOME}" append="true">
            <include name="libs/air"/>
        </compiler.library-path>
        <compiler.library-path dir="${APP_ROOT}/libs/" append="true">
            <include name="PureMVC_AS3_2_0_4/bin/PureMVC_AS3_2_0_4.swc"/>
            <include name="purePDF_0.74/bin/purePDF.swc"/>
        </compiler.library-path>
    </mxmlc>
</target>

Now you have four library-path directives:

  1. The first library-path is append="false" and thus clears out any existing library paths that have been set through the frameworks default flex-config.xml and air-config.xml. Inside that block, I include the default flex libraries.
  2. Next, I include the new updater framework.
  3. In the third step, I include the other Air-related files as they would have been by the air-config.xml file. Because the updater framework 2.5 has already been loaded, this line will not include the 2.0 updater.
  4. Finally, I add my custom libraries.

The equivalent solution for the mxmlc commandline compiler should be (not tested):

mxmlc -library-path=$(FLEX_HOME)/libs $(FLEX_HOME)/locale/{locale} \
$(FLEX45_HOME)/frameworks/libs/air/applicationupdater.swc \
$(FLEX45_HOME)/frameworks/libs/air/applicationupdater_ui.swc \
$(FLEX_HOME)/frameworks/libs/air \
... your custom libs \
... other compiler options