« AIR, Flex, and Adobe Media Player | Main | AIR ConnectionManager »
AIR Update Manager
UPDATED FOR AIR BETA 3
AIR applications are out in the world with no connection back to home unless you actively code your application to check in from time to time to say hello is there a new version of me available? I have been working on an easy system for keeping AIR applications up to date and I have come up with an UpdateManager that simplifies this process.
Here is how the UpdateManager works.
Upon load of the application the UpdateManager examines the applicationDescriptor property of the NativeApplication.nativeApplication to determine the version of the installed application. This is then stored in the currentVersion property. The constructor accepts one required argument and one optional argument. The first required argument is the URL that holds the version.xml file to test. There is an example of the version.xml file below. The second is a Boolean telling the UpdateManager whether to automatically check for an update or wait until the user triggers the check. It defaults to true. If true, it will load in this XML file, parses it and compares the version property of the XML file with the version that was parsed from the applicationDescriptor. If there is a difference, and the forceUpdate property of the version.xml file is true, the application is automatically updated. If there is a difference and forceUpdate is false, the user is alerted of the available update, given a description of the update, and given the option to update their application The UpdateManager will then perform the update using the File and FileStream classes to download and write the new air file to disk and then the Updater class to complete the update.
The UpdateManager also contains a checkForUpdate() method which will allow your application to provide a way for its user to manually trigger a check for an update. To use the UpdateManager, you will simply need to create an instance of the UpdateManager and pass in the URL of the version.xml file. You may also pass in the optional second argument.

There is also a method in the UpdateManager that will allow you to offer a way for your users to check for available updates anytime the application is running. This method will perform as stated previously if an update is available or will show a message that there are no updates available.

A sample WindowedApplication file:
2
3
4
5
6
7
8
9
10
11
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import com.everythingflex.air.managers.UpdateManager;
private var um:UpdateManager = new UpdateManager("http://www.yourdomain.com/AIR/UMTest/version.xml",false);
]]>
</mx:Script>
<mx:Button click="um.checkForUpdate()" label="Test for Update"
horizontalCenter="0" verticalCenter="0"/>
</mx:WindowedApplication>
Here is the version.xml file that needs to be on a server accessible by your AIR application. There are several properties that must be set. The currentVersion should be the same as the newest AIR package’s application.xml that you have set in the downloadLocation property. The forceUpdate property will automatically update the user without giving the user the option to say no. The message will be displayed to the user as Details: “message contents” within the Alert window. This is where you can show what new features are in the update.
version.xml file
2
3
4
5
<currentVersion version=".2"
downloadLocation="http://www.yourdomain.com/AIR/UMTest/UM.air"
forceUpdate="false"
message="Added new features"/>
The UpdateManager class
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
{
import flash.desktop.NativeApplication;
import flash.desktop.Updater;
import flash.events.Event;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.utils.ByteArray;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
public class UpdateManager
{
// URL of the remote version.xml file
private var versionURL:String;
// load in the applicationDescriptor
private var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
private var ns : Namespace = appXML.namespace();
// set the currentVersion information
private var currentVersion:String = appXML.ns::version;
// holder for remote version.xml XML data
private var version:XML;
private var urlStream:URLStream = new URLStream();
private var fileData:ByteArray = new ByteArray();
// the constructor requires the versionURL
public function UpdateManager(versionURL:String,autoCheck:Boolean=true):void{
this.versionURL = versionURL;
if(autoCheck)loadRemoteFile();
}
// load the remote version.xml file
private function loadRemoteFile():void{
var http:HTTPService = new HTTPService();
http.url = this.versionURL;
http.useProxy=false;
http.method = "GET";
http.resultFormat="xml";
http.send();
http.addEventListener(ResultEvent.RESULT,testVersion);
http.addEventListener(FaultEvent.FAULT,versionLoadFailure);
}
/*
test the currentVersion against the remote version file and either alert the user of
an update available or force the update, if no update available, alert user
*/
public function checkForUpdate():Boolean{
if(version == null){
this.loadRemoteFile();
return true;
}
if((currentVersion != version.@version) && version.@forceUpdate == true){
getUpdate();
}else if(currentVersion != version.@version){
Alert.show("There is an update available,\nwould you like to get it now? \n\nDetails:\n" + version.@message, "Choose Yes or No", 3, null, alertClickHandler);
}else{
Alert.show("There are no new updates available", "NOTICE");
}
return true;
}
/*
test the currentVersion against the remote version file and either alert the user of
an update available or force the update
*/
private function testVersion(event:ResultEvent):void{
version = XML(event.result);
if((currentVersion != version.@version) && version.@forceUpdate == true){
getUpdate();
}else if(currentVersion != version.@version){
Alert.show("There is an update available,\nwould you like to " +
"get it now? \n\nDetails:\n" + version.@message,
"Choose Yes or No", 3, null, alertClickHandler);
}
}
/*
Load of the version.xml file failed
*/
private function versionLoadFailure(event:FaultEvent):void{
Alert.show("Failed to load version.xml file from "+
this.versionURL,"ERROR");
}
// handle the Alert window decission
private function alertClickHandler(event:CloseEvent):void {
if (event.detail==Alert.YES){
getUpdate();
}
}
// get the new version from the remote server
private function getUpdate():void{
var urlReq:URLRequest = new URLRequest(version.@downloadLocation);
urlStream.addEventListener(Event.COMPLETE, loaded);
urlStream.load(urlReq);
}
// read in the new AIR package
private function loaded(event:Event):void {
urlStream.readBytes(fileData, 0, urlStream.bytesAvailable);
writeAirFile();
}
// write the newly downloaded AIR package to the application storage directory
private function writeAirFile():void {
var file:File = File.applicationStorageDirectory.resolvePath("Update.air");
var fileStream:FileStream = new FileStream();
fileStream.addEventListener(Event.CLOSE, fileClosed);
fileStream.openAsync(file, FileMode.WRITE);
fileStream.writeBytes(fileData, 0, fileData.length);
fileStream.close();
}
// after the write is complete, call the update method on the Updater class
private function fileClosed(event:Event):void {
var updater:Updater = new Updater();
var airFile:File = File.applicationStorageDirectory.resolvePath("Update.air");
updater.update(airFile,version.@version);
}
}
}
Topics: Adobe AIR | 20 Comments »


![[image]](http://mowser.com/img?url=http%3A%2F%2Fassets.max.adobe.com%2Fimages%2FMAX09_D125x125.jpg)




October 8th, 2007 at 1:04 pm
Rich, have you considered decoupling the version check from the construction of the UpdateManager object? Perhaps extending the EventDispatcher class and sending an Event.COMPLETE event when both the current version and published version resources have been loaded. Otherwise, there is no way for a consumer of the class to know when it is safe to call checkForUpdate(). If you call it too soon (say before the website version.xml has been loaded), the code thumps with null object exceptions.
Reply to this comment
October 8th, 2007 at 1:11 pm
Currently, the loadRemoteFile() method is called automatically by the processXMLData() method which is the result handler for the loading of the local file so I don’t think it is likely that the situation you are describing would occur. Unless, you are explicitly calling the checkForUpdate() method on application load. You are correct that there should probably be some error handling on the checkForUpdate() method to make sure that both the local and remote files have been loaded and parsed.
Reply to this comment
October 10th, 2007 at 12:47 am
I ran into the same problem checking for updates while loading my app. Thanks for the code!
Reply to this comment
October 12th, 2007 at 7:07 am
Thanks a lot for this very good code !
Marc
Reply to this comment
October 17th, 2007 at 5:32 pm
I created a similar update manager today also with a version XML file before coming across this entry. You mentioned you used the descriptor file version but I don’t see that in your example. Maybe I missed it but I did try using Shell.shell.applicationDescriptor but can’t seem to access the nodes using E4X, i.e. the version attribute?
I also get a “Error #2032: Stream Error” when trying to run my UpdateManager. Any ideas?
Thanks for the post.
Reply to this comment
October 17th, 2007 at 6:12 pm
Seems like it was a server permissions problem the error “Error #2032: Stream Errorâ€. I moved the installer to another server and now it is working.
Reply to this comment
October 17th, 2007 at 7:35 pm
Glad to hear all is working for you.
Reply to this comment
October 22nd, 2007 at 5:09 pm
This was a wonderful piece of code. With some small tweeks for things like error handling, it does everything I was hoping to do with the updating.
I ran into the error 2032 stuff as well. The first time i misspelled the URL. The second time the server was not setup to allow the transfer of air files (when I moved it from localhost to a live server).
Reply to this comment
October 23rd, 2007 at 12:50 pm
Thank you very much for your work. I made two changes:
- remove the loadRemoteFile() from the constructor) in order to prevent a check for update before I ever call the checkForUpdate() method.
- add an Alert in testVersion() to notify the user when no new version is available
I also localized the tests to German (in case you ever want to support locales)
package com.everythingflex.air.managers {
import flash.events.Event;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.system.Shell;
import flash.system.Updater;
import flash.utils.ByteArray;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
public class UpdateManager {
public var versionURL:String;
private var appXml:XML = Shell.shell.applicationDescriptor;
private var currentVersion:String = appXml.@version;
private var version:XML;
private var urlStream:URLStream = new URLStream();
private var fileData:ByteArray = new ByteArray();
private const WANT_UPGRADE:String = “Es ist ein Update verfügbar,\nmöchten Sie es jetzt installieren? \n\nDetails:\n”;
private const HEADER:String = “Update Manager”;
private const NO_UPGRADE:String = “Es ist kein Update verfügbar.”;
public function UpdateManager(versionURL:String):void{
this.versionURL = versionURL;
// loadRemoteFile();
}
// load the remote version.xml file
private function loadRemoteFile():void{
var http:HTTPService = new HTTPService();
http.url = this.versionURL;
http.useProxy=false;
http.method = “GET”;
http.resultFormat=”xml”;
http.send();
http.addEventListener(ResultEvent.RESULT,testVersion);
}
/*
test the currentVersion against the remote version file and either alert the user of
an update available or force the update
*/
public function testVersion(event:ResultEvent):void{
version = XML(event.result);
if((currentVersion != version.@version) && version.@forceUpdate == true){
getUpdate();
}else if(currentVersion != version.@version){
Alert.show(WANT_UPGRADE + version.@message, HEADER, 3, null, alertClickHandler);
}else{
Alert.show(NO_UPGRADE, HEADER);
}
}
/*
test the currentVersion against the remote version file and either alert the user of
an update available or force the update, if no update available, alert user
*/
public function checkForUpdate():Boolean{
if(version == null){
this.loadRemoteFile();
return true;
}
if((currentVersion != version.@version) && version.@forceUpdate == true){
getUpdate();
}else if(currentVersion != version.@version){
Alert.show(WANT_UPGRADE + version.@message, HEADER, 3, null, alertClickHandler);
}else{
Alert.show(NO_UPGRADE, HEADER);
}
return true;
}
// handle the Alert window decission
private function alertClickHandler(event:CloseEvent):void {
if (event.detail==Alert.YES){
getUpdate();
}
}
// get the new version from the remote server
private function getUpdate():void{
var urlReq:URLRequest = new URLRequest(version.@downloadLocation);
urlStream.addEventListener(Event.COMPLETE, loaded);
urlStream.load(urlReq);
}
// read in the new AIR package
private function loaded(event:Event):void {
urlStream.readBytes(fileData, 0, urlStream.bytesAvailable);
writeAirFile();
}
// write the newly downloaded AIR package to the application storage directory
private function writeAirFile():void {
var file:File = File.applicationStorageDirectory.resolvePath(”Update.air”);
var fileStream:FileStream = new FileStream();
fileStream.addEventListener(Event.CLOSE, fileClosed);
fileStream.openAsync(file, FileMode.WRITE);
fileStream.writeBytes(fileData, 0, fileData.length);
fileStream.close();
}
// after the write is complete, call the update method on the Updater class
private function fileClosed(event:Event):void {
var updater:Updater = new Updater();
var airFile:File = File.applicationStorageDirectory.resolvePath(”Update.air”);
updater.update(airFile,version.@version);
}
}
}
Reply to this comment
November 5th, 2007 at 2:36 pm
I get the error’s:
1046: Type was not found or was not a compile-time constant: UpdateManager.
1180: Call to a possibly undefined method UpdateManager.
Please someone help! It’s on line 6, this line:
private var um:UpdateManager = new UpdateManager(”http://www.slipszenko.net/flex/update/version.xml”);
Reply to this comment
November 5th, 2007 at 3:05 pm
Make sure you have imported the UpdateManager class.
Reply to this comment
November 9th, 2007 at 6:23 pm
Thanks, great example.
The only change is the version is no longer an attribute of the root node.
You’ll need something like this to access it:
appXml = Shell.shell.applicationDescriptor;
var airNS:Namespace = appXml.namespace();
currentVersion = appXml.airNS::version;
I’m sure it will change again before it is all said and done…
Reply to this comment
November 9th, 2007 at 8:10 pm
Yep, I will update all of these as well as the 16 chapters that I have already written for my Beginning AIR book as soon as Adobe freezes the API.
Reply to this comment
November 28th, 2007 at 8:00 pm
Very cool! Thanks for the code. I started putting an updater class together tonight when I found your post. Yours does everything I was looking for, so that’s some time saved. Thanks again.
Reply to this comment
January 6th, 2008 at 9:01 am
Hi,
I use actionscript 3 with flash cs3.
When i run the UpdatemManager class, I get this complie error:
1046: Type was not found or was not a compile-time constant: ResultEvent.
Anyone any idea how to fix this?
Cheers,
Vic
Reply to this comment
January 6th, 2008 at 9:27 am
ResultEvent is in the mx.rpc.events package and therefore the UpdateManager will not work for Flash based AIR applications.
Reply to this comment
January 9th, 2008 at 9:28 am
i installed and did all i am getting
“This application cannot be installed because this installer has been mis-configured. Please contact the application author for assistance.”
Reply to this comment
January 21st, 2008 at 2:28 pm
I have same problem .
“This application cannot be installed because this installer has been mis-configured. Please contact the application author for assistance.â€
Please help me.
Reply to this comment
February 4th, 2008 at 9:27 am
This is probably because you are crossing between different builds of AIR. I am not having this issue in my testing.
Reply to this comment
February 4th, 2008 at 1:47 pm
Regarding the “This application cannot be installed because this installer has been mis-configured. Please contact the application author for assistance.†error message:
This message indicates a mismatch between your invocation of the Updater API and the AIR file you are providing to it. For example, you’ll get this error message if the version number passed to the API doesn’t match the version in the AIR file. You’ll also receive this message if the old and new versions do not have matching publisher IDs.
To get a better idea of what’s going on, enable installer logging. See http://blogs.adobe.com/simplicity/2007/10/air_installer_logging.html.
Reply to this comment