본문 바로가기

공부방/Flex

[Flex] Custom AIR updater interface using ApplicationUpdater

Update

I have updated the project to work with Flash Builder 4, you can get the code at this post.

Original Post:

To tell you the truth, I never gave the ApplicationUpdaterUI much thought in terms of memory use before this post. I just thought it was a great way to add a complete update system to your AIR applications. I had not thought it could be so memory intensive until a recent conversation with Jesse Warden on Twitter:



* Please note that Jesse is dude that looks about 17 and I am the older more distinguished gentleman.

Problem with ApplicationUpdaterUI

Indeed, that “mofo eats mad RAM” is putting it lightly. It turns out that the ApplicationUpdaterUI consumes about 14MB of RAM within the application. Furthermore, for the ApplicationUpdaterUI to even check for available updates, it must loads the entire UI in memory even if not displayed. So just by using the framework, you end adding 14MB to your application that won’t be garbage collected. Granted, in most cases 14MB of RAM is not that big of deal. However, for smaller applications you might want to consider an alternative approach.

There happens to another class file called ApplicationUpdater. This class basically gives you all the benefits of the ApplicationUpdaterUI framework without any of the visible elements. This means you have to build your own user interface. This might actually be a benefit in the long run as you may want to customize your application updater to match your application. The other benefit is that you can use the ApplicationUpdater class to check for updates without loading any user interface. The interface only needs to be loaded if there is an actual update available.

Custom Updater Interface with ApplicationUpdater

In building my own custom updater interface, I thought it might be nice to have the same look and feel of the ApplicationUpdaterUI along with some of the more polite features (like postpone update until restart). Building your own updater using the ApplicationUpdater turns out to be a little more involved than I had originally thought. However, when you start attacking a problem you persevere until the end, no matter what the pain.

I did find some available information in the Adobe AIR 1.5 Cookbook, which has a basic custom updater example. This helped with some of the basic stuff, but I still needed a little more detail to make something similar to the ApplicationUpdaterUI. I found another good example by Jens Krause, but this example is for Flex 4. I needed something that would work for Flex 3. So here is what I built based on these examples:



The application consists of one class file and one MXML dialog for displaying all the user interface elements. I borrowed some of the icons from the ApplicationUpdaterUI which is available in the AIR SDK (I hope Adobe doesn’t mind). The update dialog UI is only loaded when needed, all checks for application updates happen using the ApplicationUpdater. The update dialog is only displayed when the user wants to manually update or the periodic update check finds a new application update.

The Good and the Bad

The good news is the updater only consumes about 2MB of RAM when you load the update dialog box, part of that is the ApplicationUpdater itself running in the background. The other good news is that you can yank out or customize any part of the this example to meet your own needs. The bad news is that even though I tried my best to fully remove the update dialog and have garbage collection clean it up, it doesn’t fully unload from memory (go figure). I don’t feel this is a huge issue as the application is most likely restarted after you install an update. Below is the code followed by a zip file containing the full application.

UpdateManager.as

001package com.thanksmister
002{
003    import air.update.ApplicationUpdater;
004    import air.update.events.DownloadErrorEvent;
005    import air.update.events.StatusFileUpdateErrorEvent;
006    import air.update.events.StatusFileUpdateEvent;
007    import air.update.events.StatusUpdateErrorEvent;
008    import air.update.events.StatusUpdateEvent;
009    import air.update.events.UpdateEvent;
010 
011    import flash.desktop.NativeApplication;
012    import flash.events.ErrorEvent;
013    import flash.events.Event;
014    import flash.events.ProgressEvent;
015    import flash.filesystem.File;
016 
017    public class UpdateManager
018    {
019        private var appUpdater:ApplicationUpdater;
020        private var appVersion:String;
021        private var baseURL:String;
022        private var updaterDialog:UpdaterDialog;
023        private var configurationFile:File;
024        private var isFirstRun:String;
025        private var upateVersion:String;
026        private var applicationName:String;
027        private var installedVersion:String;
028        private var description:String;
029 
030        private var initializeCheckNow:Boolean false;
031        private var isInstallPostponed:Boolean false;
032        private var showCheckState:Boolean true;
033 
034        /**
035         * Constructer for UpdateManager Class
036         *
037         * @param showCheckState Boolean value to show the Check Now dialog box
038         * @param initializeCheckNow Boolean value to initialize application and run check on instantiation of the Class
039         * */
040        public function UpdateManager(showCheckState:Boolean true, initializeCheckNow:Boolean =false)
041        {
042            this.showCheckState = showCheckState;
043            this.configurationFile = new File("app:/config/update.xml");
044            this.initializeCheckNow = initializeCheckNow;
045            initialize();
046        }
047 
048        public function checkNow():void
049        {
050            //trace("checkNow");
051            isInstallPostponed = false;
052            if(showCheckState) {
053                createDialog(UpdaterDialog.CHECK_UPDATE);
054            else {
055                appUpdater.checkNow();
056            }
057        }
058 
059        //----------  ApplicationUpdater ----------------//
060 
061        private function initialize():void
062        {
063            //trace("initialize");
064            if(!appUpdater){
065                appUpdater = new ApplicationUpdater();
066                appUpdater.configurationFile = configurationFile;
067                appUpdater.addEventListener(UpdateEvent.INITIALIZED, updaterInitialized);
068                appUpdater.addEventListener(StatusUpdateEvent.UPDATE_STATUS, statusUpdate);
069                appUpdater.addEventListener(UpdateEvent.BEFORE_INSTALL, beforeInstall);
070                appUpdater.addEventListener(StatusUpdateErrorEvent.UPDATE_ERROR, statusUpdateError);
071                appUpdater.addEventListener(UpdateEvent.DOWNLOAD_START, downloadStarted);
072                appUpdater.addEventListener(ProgressEvent.PROGRESS, downloadProgress);
073                appUpdater.addEventListener(UpdateEvent.DOWNLOAD_COMPLETE, downloadComplete);
074                appUpdater.addEventListener(DownloadErrorEvent.DOWNLOAD_ERROR, downloadError);
075                appUpdater.addEventListener(ErrorEvent.ERROR, updaterError);
076                appUpdater.initialize();
077            }
078        }
079 
080        private function beforeInstall(event:UpdateEvent):void
081        {
082            //trace("beforeInstall");
083            if (isInstallPostponed) {
084                event.preventDefault();
085                isInstallPostponed = false;
086            }
087        }
088 
089        private function updaterInitialized(event:UpdateEvent):void
090        {
091            //trace("updaterInitialized");
092            this.isFirstRun = event.target.isFirstRun;
093            this.applicationName = getApplicationName();
094            this.installedVersion = getApplicationVersion();
095 
096            if(showCheckState && initializeCheckNow) {
097                createDialog(UpdaterDialog.CHECK_UPDATE);
098            else if (initializeCheckNow) {
099                appUpdater.checkNow();
100            }
101        }
102 
103        private function statusUpdate(event:StatusUpdateEvent):void
104        {
105            //trace("statusUpdate");
106             event.preventDefault();
107             if(event.available){
108                this.description = getUpdateDescription(event.details);
109                this.upateVersion = event.version;
110 
111                if(!showCheckState) {
112                    createDialog(UpdaterDialog.UPDATE_AVAILABLE);
113                else if (updaterDialog) {
114                    updaterDialog.applicationName = this.applicationName;
115                    updaterDialog.installedVersion = this.installedVersion;
116                    updaterDialog.upateVersion = this.upateVersion;
117                    updaterDialog.description = this.description
118                    updaterDialog.updateState = UpdaterDialog.UPDATE_AVAILABLE;
119                }
120             else {
121                if (showCheckState) createDialog(UpdaterDialog.NO_UPDATE);
122             }
123        }
124 
125        private function statusUpdateError(event:StatusUpdateErrorEvent):void
126        {
127            event.preventDefault();
128            if(!updaterDialog){
129                createDialog(UpdaterDialog.UPDATE_ERROR);
130            else {
131                updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
132            }
133        }
134 
135        private function statusFileUpdate(event:StatusFileUpdateEvent):void
136        {
137            event.preventDefault();
138            if(event.available) {
139                updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
140                appUpdater.downloadUpdate();
141            else {
142                updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
143            }
144        }
145 
146        private function statusFileUpdateError(event:StatusFileUpdateErrorEvent):void
147        {
148            event.preventDefault();
149            updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;;
150        }
151 
152        private function downloadStarted(event:UpdateEvent):void
153        {
154            updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
155        }
156 
157        private function downloadProgress(event:ProgressEvent):void
158        {
159            updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
160            var num:Number = (event.bytesLoaded/event.bytesTotal)*100;
161            updaterDialog.downloadProgress(num);
162        }
163 
164        private function downloadComplete(event:UpdateEvent):void
165        {
166            event.preventDefault(); // prevent default install
167            updaterDialog.updateState = UpdaterDialog.INSTALL_UPDATE;
168        }
169 
170        private function downloadError(event:DownloadErrorEvent):void
171        {
172            event.preventDefault();
173            updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
174        }
175 
176        private function updaterError(event:ErrorEvent):void
177        {
178            updaterDialog.errorText = event.text;
179            updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
180        }
181 
182        //----------  UpdaterDialog Events ----------------//
183 
184        private function createDialog(state:String):void
185        {
186            if(!updaterDialog) {
187                updaterDialog = new UpdaterDialog();
188                updaterDialog.isFirstRun = this.isFirstRun;
189                updaterDialog.applicationName = this.applicationName;
190                updaterDialog.installedVersion = this.installedVersion;
191                updaterDialog.upateVersion = this.upateVersion;
192                updaterDialog.updateState = state;
193                updaterDialog.description = this.description;
194                updaterDialog.addEventListener(UpdaterDialog.EVENT_CHECK_UPDATE, checkUpdate);
195                updaterDialog.addEventListener(UpdaterDialog.EVENT_INSTALL_UPDATE, installUpdate);
196                updaterDialog.addEventListener(UpdaterDialog.EVENT_CANCEL_UPDATE, cancelUpdate);
197                updaterDialog.addEventListener(UpdaterDialog.EVENT_DOWNLOAD_UPDATE, downloadUpdate);
198                updaterDialog.addEventListener(UpdaterDialog.EVENT_INSTALL_LATER, installLater);
199                updaterDialog.open();
200            }
201        }
202 
203        /**
204         * Check for update.
205         * */
206        private function checkUpdate(event:Event):void
207        {
208            //trace("checkUpdate");
209            appUpdater.checkNow();
210        }
211 
212        /**
213         * Install the update.
214         * */
215        private function installUpdate(event:Event):void
216        {
217            appUpdater.installUpdate();
218        }
219 
220        /**
221         * Install the update.
222         * */
223        private function installLater(event:Event):void
224        {
225            isInstallPostponed = true;
226            appUpdater.installUpdate();
227            destoryUpdater();
228        }
229 
230        /**
231         * Download the update.
232         * */
233        private function downloadUpdate(event:Event):void
234        {
235            appUpdater.downloadUpdate();
236        }
237 
238        /**
239         * Cancel the update.
240         * */
241        private function cancelUpdate(event:Event):void
242        {
243            appUpdater.cancelUpdate();
244            destoryUpdater();
245        }
246 
247        //----------  Destroy All ----------------//
248 
249        private function destroy():void
250        {
251            if (appUpdater) {
252                appUpdater.configurationFile = configurationFile;
253                appUpdater.removeEventListener(UpdateEvent.INITIALIZED, updaterInitialized);
254                appUpdater.removeEventListener(StatusUpdateEvent.UPDATE_STATUS, statusUpdate);
255                appUpdater.removeEventListener(StatusUpdateErrorEvent.UPDATE_ERROR, statusUpdateError);
256                appUpdater.removeEventListener(UpdateEvent.DOWNLOAD_START, downloadStarted);
257                appUpdater.removeEventListener(ProgressEvent.PROGRESS, downloadProgress);
258                appUpdater.removeEventListener(UpdateEvent.DOWNLOAD_COMPLETE, downloadComplete);
259                appUpdater.removeEventListener(DownloadErrorEvent.DOWNLOAD_ERROR, downloadError);
260                appUpdater.removeEventListener(UpdateEvent.BEFORE_INSTALL, beforeInstall);
261                appUpdater.removeEventListener(ErrorEvent.ERROR, updaterError);
262 
263 
264                appUpdater = null;
265            }
266 
267            destoryUpdater();
268        }
269 
270        private function destoryUpdater():void
271        {
272            if(updaterDialog) {
273                updaterDialog.destroy();
274                updaterDialog.removeEventListener(UpdaterDialog.EVENT_CHECK_UPDATE, checkUpdate);
275                updaterDialog.removeEventListener(UpdaterDialog.EVENT_INSTALL_UPDATE, installUpdate);
276                updaterDialog.removeEventListener(UpdaterDialog.EVENT_CANCEL_UPDATE, cancelUpdate);
277                updaterDialog.removeEventListener(UpdaterDialog.EVENT_DOWNLOAD_UPDATE, downloadUpdate);
278                updaterDialog.removeEventListener(UpdaterDialog.EVENT_INSTALL_LATER, installLater);
279                updaterDialog.close();
280                updaterDialog = null;
281            }
282            isInstallPostponed = false;
283        }
284 
285        //----------  Utilities ----------------//
286 
287        /**
288         * Getter method to get the version of the application
290         *
291         * @return String Version of application
292         *
293         */
294        private function getApplicationVersion():String
295        {
296            var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
297            var ns:Namespace = appXML.namespace();
298            return appXML.ns::version;
299        }
300 
301        /**
302         * Getter method to get the name of the application file
304         *
305         * @return String name of application
306         *
307         */
308        private function getApplicationFileName():String
309        {
310            var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
311            var ns:Namespace = appXML.namespace();
312            return appXML.ns::filename;
313        }
314 
315        /**
316         * Getter method to get the name of the application, this does not support multi-language.
317         * Based on a method from Adobes ApplicationUpdaterDialogs.mxml, which is part of Adobes AIR Updater Framework
319         *
320         * @return String name of application
321         *
322         */
323        private function getApplicationName():String
324        {
325            var applicationName:String;
326            var xmlNS:Namespace=new Namespace("http://www.w3.org/XML/1998/namespace");
327            var appXML:XML=NativeApplication.nativeApplication.applicationDescriptor;
328            var ns:Namespace=appXML.namespace();
329 
330            // filename is mandatory
331            var elem:XMLList=appXML.ns::filename;
332 
333            // use name is if it exists in the application descriptor
334            if ((appXML.ns::name).length() != 0)
335            {
336                elem=appXML.ns::name;
337            }
338 
339            // See if element contains simple content
340            if (elem.hasSimpleContent())
341            {
342                applicationName=elem.toString();
343            }
344 
345            return applicationName;
346        }
347 
348        /**
349         * Helper method to get release notes, this does not support multi-language.
350         * Based on a method from Adobes ApplicationUpdaterDialogs.mxml, which is part of Adobes AIR Updater Framework
352         *
353         * @param detail Array of details
354         * @return String Release notes depending on locale chain
355         *
356         */
357        protected function getUpdateDescription(details:Array):String
358        {
359            var text:String="";
360 
361            if (details.length == 1)
362            {
363                text=details[0][1];
364            }
365            return text;
366        }
367    }
368}

UpdateDialog.mxml

001<?xml version="1.0" encoding="utf-8"?>
002<mx:Window xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml" styleName="updateDialogWindow"
003    layout="absolute" maximizable="false" resizable="false" currentState="{_updateState}"
004    clipContent="false" showStatusBar="false" width="530" height="180">
005 
006    <mx:Metadata>
007        [Event(name="checkUpdate", type="flash.events.Event")]
008        [Event(name="downloadUpdate", type="flash.events.Event")]
009        [Event(name="downloadUpdate", type="flash.events.Event")]
010        [Event(name="cancelUpdate", type="flash.events.Event")]
011    </mx:Metadata>
012 
013    <mx:Script>
014        <![CDATA[
015            public static var EVENT_CHECK_UPDATE:String "checkUpdate";
016            public static var EVENT_INSTALL_UPDATE:String "installUpdate";
017            public static var EVENT_DOWNLOAD_UPDATE:String "downloadUpdate";
018            public static var EVENT_CANCEL_UPDATE:String "cancelUpdate";
019            public static var EVENT_INSTALL_LATER:String "installLater";
020 
021            [Bindable] public static var UPDATE_DOWNLOADING:String "updateDownloading";
022            [Bindable] public static var INSTALL_UPDATE:String "installUpdate";
023            [Bindable] public static var UPDATE_AVAILABLE:String "updateAvailable";
024            [Bindable] public static var NO_UPDATE:String "noUpdate";
025            [Bindable] public static var CHECK_UPDATE:String "checkUpdate";
026            [Bindable] public static var UPDATE_ERROR:String "updateError";
027 
028            [Bindable] private var _isFirstRun:String;
029            [Bindable] private var _installedVersion:String;
030            [Bindable] private var _updateVersion:String;
031            [Bindable] private var _updateDescription:String;
032            [Bindable] private var _applicationName:String;
033            [Bindable] private var _updateState:String;
034            [Bindable] private var _errorText:String "There was an error checking for updates.";
035 
036            public function set isFirstRun(value:String):void
037            {
038                _isFirstRun = value;
039            }
040 
041            public function set installedVersion(value:String):void
042            {
043                _installedVersion = value;
044            }
045 
046            public function set upateVersion(value:String):void
047            {
048                _updateVersion = value;
049            }
050 
051            public function set updateState(value:String):void
052            {
053                _updateState = value;
054            }
055 
056            public function set applicationName(value:String):void
057            {
058                _applicationName = value;
059            }
060 
061            public function set description(value:String):void
062            {
063                _updateDescription = value;
064            }
065 
066            public function set errorText(value:String):void
067            {
068                _errorText = value;
069            }
070 
071            public function downloadProgress(value:Number):void
072            {
073                if(progressBar) progressBar.setProgress(value, 100);
074            }
075 
076            private function continueUpdate():void
077            {
078                if (this.currentState == UpdaterDialog.CHECK_UPDATE){
079                    this.dispatchEvent(new Event(EVENT_CHECK_UPDATE));
080                else if (this.currentState == UPDATE_AVAILABLE) {
081                    this.dispatchEvent(new Event(EVENT_DOWNLOAD_UPDATE));
082                }else if (this.currentState == INSTALL_UPDATE) {
083                    this.dispatchEvent(new Event(EVENT_INSTALL_UPDATE));
084                }
085            }
086 
087            private function cancelUpdate():void
088            {
089                if (this.currentState == INSTALL_UPDATE) {
090                    this.dispatchEvent(new Event(EVENT_INSTALL_LATER));
091                    return;
092                }
093                this.dispatchEvent(new Event(EVENT_CANCEL_UPDATE));
094            }
095 
096            public function destroy():void
097            {
098                iconImage.unloadAndStop(true);
099                iconImage.source = null;
100 
101                continueButton.removeEventListener(MouseEvent.CLICK, continueUpdate);
102                cancelButton.removeEventListener(MouseEvent.CLICK, cancelUpdate);
103 
104                // becaause we used skins, we have to clear them for garbage collection
106                continueButton.styleName = null;
107                cancelButton.styleName = null;
108 
109                while(this.numChildren > 0){
110                    this.removeChildAt(0);
111                }
112            }
113        ]]>
114    </mx:Script>
115 
116    <mx:states>
117        <mx:State name="{CHECK_UPDATE}">
118            <mx:AddChild position="lastChild">
119                <mx:Label x="152" y="86" text="Application:" styleName="updateDialogLabel"/>
120            </mx:AddChild>
121            <mx:AddChild position="lastChild">
122                <mx:Text x="230" y="86" text="{this._applicationName}"styleName="updateDialogText"/>
123            </mx:AddChild>
124            <mx:AddChild position="lastChild">
125                <mx:Label x="107" y="19" text="Check for updates" styleName="updateTitle" />
126            </mx:AddChild>
127            <mx:AddChild position="lastChild">
128                <mx:Text x="107" y="50" text="Allow the application to check for updates?" styleName="updateDialogText"/>
129            </mx:AddChild>
130            <mx:SetProperty name="height" value="180"/>
131        </mx:State>
132        <mx:State name="{UPDATE_AVAILABLE}">
133            <mx:SetProperty name="height" value="360"/>
134            <mx:AddChild position="lastChild">
135                <mx:Text  text="{this._installedVersion}" x="230" y="114"styleName="updateDialogText"/>
136            </mx:AddChild>
137            <mx:AddChild position="lastChild">
138                <mx:Text  text="{this._updateVersion}" x="230" y="134"styleName="updateDialogText"/>
139            </mx:AddChild>
140            <mx:AddChild position="lastChild">
141                <mx:Label x="118" y="114" text="Installed Version:"styleName="updateDialogLabel"/>
142            </mx:AddChild>
143            <mx:AddChild position="lastChild">
144                <mx:Label x="127" y="134" text="Update Version:" styleName="updateDialogLabel"/>
145            </mx:AddChild>
146            <mx:AddChild position="lastChild">
147                <mx:Label x="152" y="96" text="Application:"  styleName="updateDialogLabel"/>
148            </mx:AddChild>
149            <mx:AddChild position="lastChild">
150                <mx:Text x="230" y="96" text="{this._applicationName}"styleName="updateDialogText"/>
151            </mx:AddChild>
152            <mx:AddChild position="lastChild">
153                <mx:Label x="107" y="19" text="Update Available" styleName="updateTitle"/>
154            </mx:AddChild>
155            <mx:AddChild position="lastChild">
156                <mx:Text x="107" y="50" text="An updated version of the application is available for download." styleName="updateDialogText"/>
157            </mx:AddChild>
158 
159            <mx:AddChild position="lastChild">
160                <mx:Label id="releaseLabel" x="10" y="222" text="Release notes"styleName="updateDialogLabel"/>
161            </mx:AddChild>
162            <mx:AddChild position="lastChild">
163                <mx:TextArea text="{_updateDescription}" x="10" y="248" width="508" height="100"styleName="updateDialogTextArea"/>
164            </mx:AddChild>
165 
166            <mx:AddChild position="lastChild">
167                <mx:HRule x="10" y="214" width="508" styleName="updateDialogHRule"/>
168            </mx:AddChild>
169            <mx:SetProperty target="{cancelButton}" name="y" value="164"/>
170            <mx:SetProperty target="{continueButton}" name="y" value="164"/>
171            <mx:SetProperty target="{cancelButton}" name="label" value="Download later"/>
172            <mx:SetProperty target="{continueButton}" name="x" value="269"/>
173            <mx:SetProperty target="{continueButton}" name="label" value="Download now"/>
174        </mx:State>
175        <mx:State name="{NO_UPDATE}">
176            <mx:AddChild position="lastChild">
177                <mx:Label x="122" y="86" text="Application:" styleName="updateDialogLabel"/>
178            </mx:AddChild>
179            <mx:AddChild position="lastChild">
180                <mx:Text x="200" y="86" text="{this._applicationName}"styleName="updateDialogText"/>
181            </mx:AddChild>
182            <mx:AddChild position="lastChild">
183                <mx:Label x="107" y="19" text="No Updates" styleName="updateTitle"/>
184            </mx:AddChild>
185            <mx:AddChild position="lastChild">
186                <mx:Text x="107" y="50" text="There is no application update available at this time." styleName="updateDialogText"/>
187            </mx:AddChild>
188            <mx:RemoveChild target="{continueButton}"/>
189            <mx:SetProperty target="{cancelButton}" name="label" value="Close"/>
190            <mx:SetProperty name="height" value="180"/>
191        </mx:State>
192        <mx:State name="{UPDATE_DOWNLOADING}">
193            <mx:AddChild position="lastChild">
194                <mx:ProgressBar x="107" y="84" width="411" label=" " id="progressBar"mode="manual" height="15" styleName="updateDiallogProgress"/>
195            </mx:AddChild>
196            <mx:AddChild position="lastChild">
197                <mx:Text x="107" y="20" text="Downloading Update" styleName="updateTitle"/>
198            </mx:AddChild>
199            <mx:RemoveChild target="{continueButton}"/>
200            <mx:AddChild position="lastChild">
201                <mx:Label x="107" y="53" text="Download progress..." styleName="updateDialogLabel"/>
202            </mx:AddChild>
203            <mx:SetProperty name="height" value="180"/>
204        </mx:State>
205        <mx:State name="{UPDATE_ERROR}">
206            <mx:AddChild position="lastChild">
207                <mx:Label x="107" y="19" text="Unexpected error" styleName="updateTitle"/>
208            </mx:AddChild>
209            <mx:AddChild position="lastChild">
210                <mx:Label x="107" y="50" text="{_errorText}" styleName="updateDialogLabel"/>
211            </mx:AddChild>
212            <mx:RemoveChild target="{continueButton}"/>
213            <mx:SetProperty target="{cancelButton}" name="label" value="Close"/>
214            <mx:SetProperty name="height" value="180"/>
215        </mx:State>
216        <mx:State name="{INSTALL_UPDATE}">
217            <mx:AddChild position="lastChild">
218                <mx:Text  text="{this._installedVersion}" x="230" y="139"styleName="updateDialogText"/>
219            </mx:AddChild>
220            <mx:AddChild position="lastChild">
221                <mx:Text  text="{this._updateVersion}" x="230" y="159"styleName="updateDialogText"/>
222            </mx:AddChild>
223            <mx:AddChild position="lastChild">
224                <mx:Label x="118" y="139" text="Installed Version:"styleName="updateDialogLabel"/>
225            </mx:AddChild>
226            <mx:AddChild position="lastChild">
227                <mx:Label x="127" y="159" text="Update Version:"  styleName="updateDialogLabel"/>
228            </mx:AddChild>
229            <mx:AddChild position="lastChild">
230                <mx:Label x="152" y="121" text="Application:" styleName="updateDialogLabel"/>
231            </mx:AddChild>
232            <mx:AddChild position="lastChild">
233                <mx:Text x="230" y="121" text="{this._applicationName}"styleName="updateDialogText"/>
234            </mx:AddChild>
235            <mx:AddChild position="lastChild">
236                <mx:Label x="107" y="19" text="Install update" id="windowTitle4"styleName="updateTitle"/>
237            </mx:AddChild>
238            <mx:AddChild position="lastChild">
239                <mx:Text x="107" y="50" text="The update for the application is downloaded and ready to be installed." styleName="updateDialogText"/>
240            </mx:AddChild>
241 
242            <mx:AddChild position="lastChild">
243                <mx:Label x="10" y="262" text="Release notes" styleName="updateDialogLabel"/>
244            </mx:AddChild>
245            <mx:AddChild position="lastChild">
246                <mx:TextArea id="relaeseNotesTextArea0" text="{_updateDescription}" x="10"y="288" width="508" height="100" styleName="updateDialogTextArea"/>
247            </mx:AddChild>
248            <mx:SetProperty name="height" value="402"/>
249            <mx:AddChild position="lastChild">
250                <mx:ProgressBar id="installProgressBar" x="107" y="84" width="411" label=" "mode="manual" height="15" styleName="installDiallogProgress"creationComplete="installProgressBar.setProgress(100,100)"/>
251            </mx:AddChild>
252            <mx:AddChild position="lastChild">
253                <mx:HRule x="10" y="249" width="508" styleName="updateDialogHRule"/>
254            </mx:AddChild>
255            <mx:SetProperty target="{cancelButton}" name="label" value="Postpone until restart"/>
256            <mx:SetProperty target="{cancelButton}" name="y" value="198"/>
257            <mx:SetProperty target="{continueButton}" name="y" value="198"/>
258            <mx:SetProperty target="{continueButton}" name="x" value="320"/>
259            <mx:SetProperty target="{continueButton}" name="label" value="Install update"/>
260        </mx:State>
261    </mx:states>
262 
263    <mx:Button x="107" y="129" label="Cancel" id="cancelButton" click="cancelUpdate()"height="34" styleName="updateDialogButton"/>
264    <mx:Button x="202" y="129" label="Check for Updates" id="continueButton"click="continueUpdate()" height="34" styleName="updateDialogButton"/>
265    <mx:Image source="@Embed('/assets/images/UpdateIcon.png')" x="15" y="25" width="81"height="74" id="iconImage"/>
266 
267</mx:Window>

Main.mxml

01<?xml version="1.0" encoding="utf-8"?>
02<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
03    layout="vertical" width="428" height="280" creationComplete="init()">
04 
05    <mx:Style source="assets/styles.css"/>
06 
07    <mx:Script>
08        <![CDATA[
09            import com.thanksmister.UpdateManager;
10            import mx.rpc.events.ResultEvent;
11 
12            private var updater:UpdateManager;
13            [Bindable] private var baseURL:String;
14            [Bindable] private var updates:String;
15 
16            private function init():void
17            {
18                configService.send();
19            }
20 
21            private function handleResult(event:ResultEvent):void
22            {
23                var xml:XML = event.result as XML;
24                baseURL = xml..baseurl.toString();
25                updates = xml..updates.toString();
26 
27                if(updates)
28                    updater = new UpdateManager(truefalse);
29            }
30        ]]>
31    </mx:Script>
32 
33    <mx:HTTPService id="configService" method="GET" resultFormat="e4x"url="config/configuration.xml" result="handleResult(event)" />
34 
35    <mx:Text fontSize="14" color="0xFFFFFFF" text="Update tester.  Please click the button below to begin update checking or wait for the dealy time (3 min). Once the application is successfully updated, the application version will be updated from v1 to v2." x="10" y="10" width="342"height="104"/>
36 
37    <mx:Button label="Begin update process" color="0xFFFFFFF"  x="125" y="104"click="updater.checkNow()"/>
38 
39    <mx:Label id="versionText" fontSize="14" color="0xFFFFFFF"/>
40    <mx:Label text="{'Base URL: ' + baseURL}" color="0xFFFFFFF"/>
41    <mx:Label text="{'Updates: ' + updates}" color="0xFFFFFFF"/>
42</mx:WindowedApplication>

I packed up the Flex project. You would use this basically the same as you would use the ApplicationUpdaterUI framework. You need to create an update version of your AIR file and place it and update.xm file on a server. You can test it from the local IDE if you replace the server url with “app:/”. Just remember you can not actually update an AIR application from the IDE, it has to be installed and running on your machine to update, and the update files have to be accessible just like when you use the ApplicationUpdaterUI.

The code is free to use, hose, rip apart, just post back any fixes or enhancements you make.

Source Files

UpdateTester.zip

Update

Don’t forget to add a closing event handler to the dialog box that calls the destory() function. This was not in the original code and there needs to be a way to handle users clicking on the close box of the Window for the update dialog user interface. I also added a change that if you don’t want to show the check for update now option, the “no update available” dialog does not appear either, here is the updated code for statusUpdate function in UpdateManager.as (not in the downloaded zip):

01private function statusUpdate(event:StatusUpdateEvent):void
02        {
03            //trace("statusUpdate");
04             event.preventDefault();
05             if(event.available){
06                this.description = getUpdateDescription(event.details);
07                this.upateVersion = event.version;
08 
09                if(!showCheckState) {
10                    createDialog(UpdaterDialog.UPDATE_AVAILABLE);
11                else if (updaterDialog) {
12                    updaterDialog.applicationName = this.applicationName;
13                    updaterDialog.installedVersion = this.installedVersion;
14                    updaterDialog.upateVersion = this.upateVersion;
15                    updaterDialog.description = this.description
16                    updaterDialog.updateState = UpdaterDialog.UPDATE_AVAILABLE;
17                }
18             else {
19                if (showCheckState) createDialog(UpdaterDialog.NO_UPDATE);
20             }
21        }

-Mister