Spark Button and ButtonBar with icons and rollover states

The Flash Builder Spark Button control doesn’t come with an icon property out of the box. So you have to extend the Button class and add your own. I created a Spark Skin to add two icons to the Button control, one for the up/disabled state and one for the over/down states.

The Spark ButtonBar control does accommodate an icon, but there is no way to change the icon when the selected index changes. So to change the icon of the selected item, I built a Spark Skin for the ButtonBarButton and the ButtonBar to accomplish the job. Here is the running example of the buttons in action:

IconButtons.swf

The code of the IconButton class and the IconButtonSkin mxml file that accompanies the class:

https://gist.github.com/946886

And the code of the IconButtonBarButton class and the IconButtonBarSkin and IconButtonBarButtonskin mxml

https://gist.github.com/947130

You can also download the IconButtons Flash Builder Project.

-Mr

Upload to S3 with cURL and AIR NativeProcess

This application demonstrates how to upload files to Amazon S3 (part of Amazon Web Services) with cURL from an Adobe AIR application using the AIR 2 NativeProcess API. This application uses the AIR 2.0 or higher SDK, Flash Builder 4, and the cURL native application for uploading files.

To use this application also depends on the as3corelib, as3awss3lib, and as3crypto libraries. You will also need an Amazon S3 developer account and have cURL installed on your system.

Why upload with cURL instead of AIR? Part of the problem with uploading through an AIR application, or any Flash application, is the size limit of the uploads (I think something like 100MB is the recommended size). What happens when you want to upload gigabytes of data? Offloading the uploading of large files to a native process solves this problem. You can also spawn multiple instances of the native process to do multiple uploads. This makes sure your AIR application continues to be responsive and performs well while the uploads happen in the background. Using something like cURL means you have a cross-platform native process that can be installed along with your application.

The main guts of the application revolve around assembling the correct cURL arguments to upload the files to the S3 service. This also requires properly creating an policy expected by S3 to complete the upload process. Here is the core code for creating the S3 policy file and the arguments for cURL:

/**
* Uploads a file to S3 using cURL using the AIR NativeProcess API.
*
* @param file File object
* */
protected function saveFile(folderid:String, file:File):void
{
createProgressPanel(); // add our progress bar

var cURL:File = File.applicationDirectory;

if (Capabilities.os.toLowerCase().indexOf("win") > -1) {
cURL = cURL.resolvePath("bin/curl.exe");
} else if (Capabilities.os.toLowerCase().indexOf("mac") > -1) {
cURL = cURL.resolvePath("/usr/bin/curl");
}

var contentType:String = "multipart/form-data";
var arguments:Vector. = getArguments("PUT", folderid, file, contentType);

var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
nativeProcessStartupInfo.arguments = arguments;
nativeProcessStartupInfo.executable = cURL;

process = new NativeProcess();
process.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, onInputProgress);
process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onStandardOutputData);
process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onStandardErrorData);
process.addEventListener(NativeProcessExitEvent.EXIT, onStandardOutputExit);

process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, onOutputIOError);
process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, onStandardIOError);

process.start(nativeProcessStartupInfo);
}

/**
* Create the native process starupt info arguments. Be sure to check the cURL documentation
* for more functions on upload: http://curl.haxx.se/docs/manual.html
*
* @param method POST or GET arguements
* @param bucketname The S3 bucket name for upload target
* @param file File to upload
* @param contentType The mime type for the upload
* @param secure Boolean value for usting https or http
* @return Vector.
* */
protected function getArguments(method:String, bucketname:String, file:File, contentType:String = "multipart/form-data", secure:Boolean = false):Vector.
{
var protocol:String = (secure)? "https":"http";
var path:String = protocol + "://" + bucketname + "." + AMAZON_ENDPOINT
var policy:String = getPolicy(bucketname, contentType);

var arguments:Vector. = new Vector.();
arguments.push("-#"); // gives us a ### % ouput for progress from cURL
arguments.push("-F key=" + file.name );
arguments.push("-F AWSAccessKeyId=" + this.accessKey );
arguments.push("-F policy=" + policy );
arguments.push("-F signature=" + getSignature( policy) );
arguments.push("-F Content-Type=" + contentType);
arguments.push("-F file=@" + file.nativePath);
arguments.push(path);

return arguments;
}

/**
* Creates the policy file for the S3 upload. For more information on AWS policy files:
* http://aws.amazon.com/articles/1434. The paramater content-length-range restricts
* the file upload size. Remove it if you want to have no restrictions on upload size.
* */
protected function getPolicy(bucketname:String, contentType:String):String
{
// date has to be some time in the future so uploads don't expire in progress
var obj:Object = {"expiration": "2015-06-15T12:00:00.000Z",
"conditions": [
{"bucket": bucketname},
["starts-with", "$key", ""],
["starts-with", "$Content-Type", ""],
["content-length-range", 0, 1048576]
]
}

var json:String = JSON.encode(obj);
var encoded: String = Base64.encode(json);

return encoded;
}

/**
* Craete the signature for S3. For more information on S3 signatures:
* http://aws.amazon.com/articles/1434
* */
protected function getSignature(policy:String):String
{
var policyBytes:ByteArray = new ByteArray();
policyBytes.writeUTFBytes(policy);

var secretAccessKeyBytes:ByteArray = new ByteArray();
secretAccessKeyBytes.writeUTFBytes(this.secretAccessKey);

var hmacBytes:ByteArray = hmac.compute(secretAccessKeyBytes, policyBytes);

return Base64.encodeByteArray(hmacBytes);
}

The tricky part was getting upload feedback from cURL in a format that we could use to display the upload progress. You might notice in the code above that we add an argument to cURL to output a % for uploads in progress. We need to parse this information when it comes back in from the ProgressEvent.STANDARD_ERROR_DATA event of the NativeProcess:

/**
* Handles writing within the process such as percent complete.
* */
protected function onStandardErrorData(event:ProgressEvent):void
{
var output:String = ( process.standardError.readUTFBytes(process.standardError.bytesAvailable) );
var regex:RegExp = /([0-9\.]+)/;
var exec:String = regex.exec(output);

if(exec) {
var arry:Array = exec.split(",");
var percent:Number = Math.round(Number(arry[0])); // save percent complete

if(percent > percentComplete) {
percentComplete = percent;
progressBar.setProgress(percentComplete, progressBar.maximum);
}
}
}

The AIR application installer file and the full code for this example can be found at the GitHub repository:

AWSS3cURL

Additional Resources
Amazon Dev Article on Form Post Upload
Amazon S3 Forum discussion on cURL
cURL
Amazon S3 Manager

- Mister

AIR for Android: Phoenix Traffic Released in Android Market


Phoenix Traffic for Android

I finally released my first application into the Android Market. This application takes data for Phoenix traffic cameras and displays live camera images. That part was simple enough, but deciding to go with either Flex or Flash for development was the hard part. Originally, I built the application using Flex 4 (this was pre-Hero and Burrito) and talked about it in a previous post. However, I found the Flex application to be memory intensive, lethargic, and the scrolling list functionality simply unusable.

For the final release of the application, I decided to use Flash CS5. This allowed me to build a somewhat smaller version of the application that could be easily be deployed using Adobe’s AIR technology for Android directly from Flash. As part of this project I built (or rather modified code from others) an AS3 scrolling list that responds to touch events and behaves similar to native Android scrolling lists.

In my opinion, the list still performs better than some of the current controls in being built for Flex Burrito. This might change in the near future, but for now, AS3 only applications on Android seem to be lighter and perform better than Flex. If you are interested, you can see the code and example files for the scrolling list here in this post.

To download the Phoenix Traffic application, you can visit the Phoenix Traffic application page from this site or just search for “Phoenix Traffic” in the Android Market. Please feel free to leave me any comments or feedback about the application. I will continue to tinker with the application and improve it over time.

Update

This application has since been migrated from AIR to a native Android application.

- Mister

Phoenix Traffic Android Mobile Application built with AIR

UPDATE 4/04/2012
The application has been migrated from Adobe AIR for Android to a native Android application. Still available in the market under the same name.

UPDATE
I recently released the Phoenix Traffic application on the Android market. The application was built using CS5 rather than Flex. I also created my own custom scrolling list that works in AS3 for iPhone and Android. You can find out more on this post:

Phoenix Traffic Released in Android Market

AS3 Scrolling List for Android and iOS

Original Post

I developed a simple Flex 4 application targeting AIR for Android. For this application, I used Flex to build a somewhat smaller version of a previous application that uses XML data to display a list Phoenix traffic cameras locations and images. The images are updated on an set interval and locations are selected from a list of freeways and intersections for each traffic camera.

Initially, I built the application in Flash, but ran into some issues and annoyances with creating a proper list that worked with the Multitouch events for capturing user gestures on multitouch devices like Android.

The Flex 4 list has support for TouchEvent. However, it’s still difficult to differentiate between the different TouchEvent’s being fired (TouchEvent.TOUCH_BEGIN, TouchEvent.TOUCH_MOVE, TouchEvent.TOUCH_END, TouchEvent.TAP). As a result, the list has a tendency to select items while scrolling the list.

Here are some screen shots of the application running on my NexusOne:

 

The Flex preloader was replaced with a simple splash screen graphic.

Simple list of freeways.

Once you select a freeway, you see list of camera locations for that freeway.

Selecting a camera takes displays the live camera image accompanied by two still images for direction.

This was my first run at doing Android and I can see some potential. Deploying AIR application from Flex 4 to your Android device is pretty easy, though nothing like the experience publishing from Flash CS5. The applications tend to be a little robust, hopefully this will be resolved with the release of the Halo components which are designed specifically for mobile devices.

To build your own AIR for Android applications, you need an Android device running Android 2.2 which supports Flash Player 10.1 and AIR. you also need to sign up for the AIR for Android Prerelease program. There are some good example applications with code starting to appear, including a scrolling list. Here are the posts I referenced used building this application:

Flex 4 List Scrolling on Android with Flash Player 10.1
“VoiceNotes for Android”: Sample App using Flex, AIR, and the Microphone API
AIR on Android: TweetrApp Video walk-through
Employee Directory Sample Application Using Flex and AIR for Android – Updated for Froyo
Flex 4 Application Handling Touch Events on Android with Flash Player 10.1

- Mister

Screen Capture with AIR 2 NativeProcess

Prior to AIR 2 the only way to capture the screen was to use something like Marapi Java Bridge that acts as a conduit between your application and the native system.

However, with AIR 2 Beta and the upcoming AIR 2 release, you can now use the command-line tool “screencapture” that comes with Mac OS. Nothing extra needs to be bundled with the application for this to work on a Mac, but on Windows, you need an additional piece of code to execute the screen capture process.

The process of doing a screen capture is pretty easy. You first create a File object that points to the location of the “screencapture” command line on the Mac:

var file:File = File.applicationDirectory;
file = file.resolvePath("usr/sbin/screencapture");

Then create a NativeProcessStartupInfo object that will be used when we start the NativeProcess.The arguments property of the NativeProcessStartupInfo takes a list of arguments that will be passed to the command line tool when its started. For a complete list of arguments used by the screencapture command line tool, type “screencapture –help” in the terminal window. For this example I wanted to push out a file named “screencap.png” to the desktop from a selection of the screen. Optionally, you could capture the image to the clipboard and paste it directly.

var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
var args:Vector. = new Vector.();
args[0] = "-i";
args[1] = "screencapture.png";

nativeProcessStartupInfo.arguments = args;
nativeProcessStartupInfo.executable = file;

Now we can start the native process:

process = new NativeProcess();
process.start(nativeProcessStartupInfo);

The screencapture command tool will be launched and you will see the selection cursor on your screen. After you make a selection, the file will automatically be saved to the desktop as “screencapture.png”.

Here is the complete project code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/mx"
                       creationComplete="init()">
    <fx:Script>
        <![CDATA[
            private var process:NativeProcess;
           
            private function init():void
            {
                launchNativeProcess();
            }
           
            private function launchNativeProcess():void
            {
                if(NativeProcess.isSupported) {
                   
                    var file:File = File.applicationDirectory;
                   
                    if (Capabilities.os.toLowerCase().indexOf("win") > -1) {
                        //file = file.resolvePath("bin/mylocalexe.exe");
                    } else if (Capabilities.os.toLowerCase().indexOf("mac") > -1) {
                        file = file.resolvePath("/usr/sbin/screencapture");
                    }
                   
                    var args:Vector.<String> = new Vector.<String>();
                        args[0] = "-i";
                        args[1] = "screencapture.png";
                   
                    var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
                        nativeProcessStartupInfo.arguments = args;
                        nativeProcessStartupInfo.executable = file;
                        nativeProcessStartupInfo.workingDirectory = File.desktopDirectory;
                   
                    process = new NativeProcess();
                    process.start(nativeProcessStartupInfo);
                   
                } else {
                    trace("Native Process not supported");
                }
            }
        ]]>
    </fx:Script>
   
</s:WindowedApplication>

Don’t forget to include support for extended desktop in the application xml file:

<supportedProfiles>extendedDesktop</supportedProfiles>

To get something like this to work on Windows, you need to know a little bit of C or C# so you can call your own service and launch the screen print functionality on Windows. For more about using the NativeProcess in AIR 2 for both Windows and Mac, you check out this Adobe article.

-Mr

AS3 Yammer Library

For the past few months I have been working on an AS3 library that wraps the Yammer API for use in Flash, Flex, and AIR projects. The biggest hurdle was making the application work for the web. As it is now, for Flash projects, you still need to be targeting Flash player 10 so that you can upload the ByteArray information file attachments. The second hurdle was creating an easy Oauth flow that doesn’t add items to the header that would violate the Flash player security, something you don’t have to worry about when creating AIR applications.

With that work out of the way, I would like to introduce an AS3 Yammer API library that communicates with the Yammer service. The as3yammerlib is hosted on GitHub and the source code is publicly available. To use the library you will need your own consumer key and secret for the Yammer API available from Yammer’s developer site. The library is also dependent on the as3corelib for doing JSON parsing and as3-signals for broadcasting events.

I put together a Flex web-based example using Flex Builder 4 that walks you through the Oauth process and retrieves messages for the logged in user. To use the code, just create a new Flex project and paste the Main.mxml file in your main application file and create another mxml for the MessageItemRenderer file within the same directory:

Main.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo"
               xmlns:ns="library://ns.adobe.com/flex/mx"
               creationComplete="init()"
               minWidth="1024" minHeight="768">


    <fx:Script>
        <![CDATA[
            import com.yammer.api.Yammer;
            import com.yammer.api.events.YammerEvent;
            import com.yammer.api.vo.Oauth;
            import com.yammer.api.vo.YammerCurrentUser;
            import com.yammer.api.vo.YammerError;
            import com.yammer.api.vo.YammerMessageList;
            import com.yammer.api.vo.YammerTab;

            import mx.collections.ArrayCollection;
            import mx.controls.Alert;

            private var oauthToken:String;
            private var oauthSecret:String;

            // your consumer key and secret go here
            private var consumerKey:String = "your consumer key";
            private var consumerSecret:String = "your consumer secret";

            private var service:Yammer;

            [Bindable] private var currentUser:YammerCurrentUser;

            private function init():void
            {
                this.service = new Yammer(this.consumerKey, this.consumerSecret);
                this.service.errorReceived.add(errorReceived);

                // we have tokens stored
                if(oauthToken && oauthSecret){
                    service.setOuthTokens(oauthToken, oauthSecret);
                    this.getCurrentUser();
                }
            }

            // get request token from service.
            private function getRequestToken():void
            {
                service.resultsReceived.add(requestTokenReceived);
                service.requestToken();
            }

            // submit the virify pin from the web site
            private function getAccessToken(verify_pin:String):void
            {
                service.resultsReceived.add(accessTokenReceived);
                service.accessToken(verify_pin);
            }

            // sends user to browser page to retrieve verification pin.
            private function sendAuthorizationRequest():void
            {
                service.sendAuthorizationRequest(); // open browser window
            }

            private function getCurrentUser():void
            {
                service.resultsReceived.add(currentUserReceived);
                service.getCurrentUser();
                return;
            }

            public function getMessages(tab:YammerTab):void
            {
                service.resultsReceived.add(messageListReceived);
                service.getMessages(tab.url);
                return;
            }

            private function requestTokenReceived(value:Oauth):void
            {
                service.resultsReceived.remove(requestTokenReceived);
                Alert.show("Request tokens received: " + value.oauth_token);
            }

            private function accessTokenReceived(value:Oauth):void
            {
                service.resultsReceived.remove(accessTokenReceived);
                Alert.show("Access tokens received:: token: " + value.oauth_token + " secret: " + value.oauth_token_secret);
                this.getCurrentUser();
            }

            private function currentUserReceived(value:YammerCurrentUser):void
            {
                service.resultsReceived.remove(currentUserReceived);
                currentUser = value;
                var tab:YammerTab = currentUser.tabs[0]; // get first user tab, usually My Feed
                this.getMessages(tab);
            }

            private function messageListReceived(value:YammerMessageList):void
            {
                service.resultsReceived.remove(messageListReceived);
                var messagelist:YammerMessageList = value;
                var messages:ArrayCollection = new ArrayCollection(messagelist.getMessages());
                messageList.dataProvider = messages;
            }

            private function errorReceived(error:YammerError):void
            {
                Alert.show("Yammer Service Error: " + error.errorMessage);
            }

        ]]>
    </fx:Script>

    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>

    <s:Button label="1. Request Token" click="getRequestToken()" width="120" x="10" y="10"/>

    <s:Button label="2. Open Browser" click="sendAuthorizationRequest()" width="120" x="138" y="10"/>

    <s:TextInput id="pinInput" text="3. Paste Pin" width="120" textAlign="center" focusIn="pinInput.text = ''" x="266" y="10"/>

    <s:Button label="4. Access Token" click="getAccessToken(pinInput.text)" width="120" x="394" y="10"/>

    <s:List id="messageList" x="10" y="113" width="563" height="337" itemRenderer="MessageItemRenderer"/>

    <ns:Image source="{currentUser.mugshot_url}" width="48" height="48" x="11" y="45"/>

    <s:Label text="{currentUser.full_name}" x="67" y="50"  fontSize="14"/>

    <s:Label text="{currentUser.job_title}" x="67" y="72"  fontSize="11"/>

</s:Application>

MessageItemRenderer.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer width="100%"
           xmlns:fx="http://ns.adobe.com/mxml/2009"
           xmlns:s="library://ns.adobe.com/flex/spark"
           xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:ns="library://ns.adobe.com/flex/mx">

    <fx:Script>
        <![CDATA[
            import com.yammer.api.utils.MessageUtilities;
            import com.yammer.api.vo.YammerMessage;

            [Bindable] private var message:YammerMessage;

            override public function set data(value:Object):void
            {
                super.data = value;
                message = data as YammerMessage;
            }

            /**
             *  override set data to set
             */
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
            {
                super.updateDisplayList(unscaledWidth,unscaledHeight);
            }
        ]]>
    </fx:Script>

    <s:Rect left="0" bottom="1" right="0" height="1">
        <s:fill>
            <s:SolidColor color="0x000000" alpha=".2"/>
        </s:fill>
    </s:Rect>

    <s:HGroup width="100%" height="100%" paddingTop="5" paddingBottom="5" paddingRight="5" paddingLeft="5">

        <ns:Image source="{message.sender.mugshot_url}" width="48" height="48"/>

        <s:VGroup width="100%" height="100%">

            <s:Label  text="{message.sender.full_name}" maxDisplayedLines="1" left="10" top="10" right="26"  fontWeight="bold" color="#000000"/>

            <s:RichEditableText id="richEdTxt" text="{message.body_plain}" width="100%" editable="false" selectable="false" focusEnabled="false" />

            <s:HGroup width="100%" paddingTop="2">
                <s:Label text="{message.created_at}"/>
            </s:HGroup>

        </s:VGroup>

    </s:HGroup>

</s:ItemRenderer>

I am sure there are lots of ways to improve the library, but this one is a pretty simple start with lots of functionality. Please let me know if you find any errors and feel free to fork this version and add any improvements to the code.

-Mr

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

package com.thanksmister
{
	import air.update.ApplicationUpdater;
	import air.update.events.DownloadErrorEvent;
	import air.update.events.StatusFileUpdateErrorEvent;
	import air.update.events.StatusFileUpdateEvent;
	import air.update.events.StatusUpdateErrorEvent;
	import air.update.events.StatusUpdateEvent;
	import air.update.events.UpdateEvent;

	import flash.desktop.NativeApplication;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;

	public class UpdateManager
	{
		private var appUpdater:ApplicationUpdater;
		private var appVersion:String;
		private var baseURL:String;
		private var updaterDialog:UpdaterDialog;
		private var configurationFile:File;
		private var isFirstRun:String;
		private var upateVersion:String;
		private var applicationName:String;
		private var installedVersion:String;
		private var description:String;

		private var initializeCheckNow:Boolean = false;
		private var isInstallPostponed:Boolean = false;
		private var showCheckState:Boolean = true;

		/**
		 * Constructer for UpdateManager Class
		 *
		 * @param showCheckState Boolean value to show the Check Now dialog box
		 * @param initializeCheckNow Boolean value to initialize application and run check on instantiation of the Class
		 * */
		public function UpdateManager(showCheckState:Boolean = true, initializeCheckNow:Boolean = false)
		{
			this.showCheckState = showCheckState;
			this.configurationFile = new File("app:/config/update.xml");
			this.initializeCheckNow = initializeCheckNow;
			initialize();
		}

		public function checkNow():void
		{
			//trace("checkNow");
			isInstallPostponed = false;
			if(showCheckState) {
				createDialog(UpdaterDialog.CHECK_UPDATE);
			} else {
				appUpdater.checkNow();
			}
		}

		//----------  ApplicationUpdater ----------------//

		private function initialize():void
		{
			//trace("initialize");
			if(!appUpdater){
				appUpdater = new ApplicationUpdater();
				appUpdater.configurationFile = configurationFile;
				appUpdater.addEventListener(UpdateEvent.INITIALIZED, updaterInitialized);
				appUpdater.addEventListener(StatusUpdateEvent.UPDATE_STATUS, statusUpdate);
				appUpdater.addEventListener(UpdateEvent.BEFORE_INSTALL, beforeInstall);
				appUpdater.addEventListener(StatusUpdateErrorEvent.UPDATE_ERROR, statusUpdateError);
				appUpdater.addEventListener(UpdateEvent.DOWNLOAD_START, downloadStarted);
				appUpdater.addEventListener(ProgressEvent.PROGRESS, downloadProgress);
				appUpdater.addEventListener(UpdateEvent.DOWNLOAD_COMPLETE, downloadComplete);
				appUpdater.addEventListener(DownloadErrorEvent.DOWNLOAD_ERROR, downloadError);
				appUpdater.addEventListener(ErrorEvent.ERROR, updaterError);
				appUpdater.initialize();
			}
		}

		private function beforeInstall(event:UpdateEvent):void
		{
			//trace("beforeInstall");
			if (isInstallPostponed) {
				event.preventDefault();
				isInstallPostponed = false;
			}
		}

		private function updaterInitialized(event:UpdateEvent):void
		{
			//trace("updaterInitialized");
			this.isFirstRun = event.target.isFirstRun;
			this.applicationName = getApplicationName();
			this.installedVersion = getApplicationVersion();

			if(showCheckState &amp;&amp; initializeCheckNow) {
				createDialog(UpdaterDialog.CHECK_UPDATE);
			} else if (initializeCheckNow) {
				appUpdater.checkNow();
			}
		}

		private function statusUpdate(event:StatusUpdateEvent):void
		{
			//trace("statusUpdate");
			 event.preventDefault();
			 if(event.available){
			 	this.description = getUpdateDescription(event.details);
			 	this.upateVersion = event.version;

			 	if(!showCheckState) {
			 		createDialog(UpdaterDialog.UPDATE_AVAILABLE);
			 	} else if (updaterDialog) {
			 		updaterDialog.applicationName = this.applicationName;
					updaterDialog.installedVersion = this.installedVersion;
					updaterDialog.upateVersion = this.upateVersion;
			 		updaterDialog.description = this.description
			 		updaterDialog.updateState = UpdaterDialog.UPDATE_AVAILABLE;
			 	}
			 } else {
			 	if (showCheckState) createDialog(UpdaterDialog.NO_UPDATE);
			 }
		}

		private function statusUpdateError(event:StatusUpdateErrorEvent):void
		{
			event.preventDefault();
			if(!updaterDialog){
				createDialog(UpdaterDialog.UPDATE_ERROR);
			} else {
				updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
			}
		}

		private function statusFileUpdate(event:StatusFileUpdateEvent):void
		{
			event.preventDefault();
			if(event.available) {
				updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
				appUpdater.downloadUpdate();
			} else {
				updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
			}
		}

		private function statusFileUpdateError(event:StatusFileUpdateErrorEvent):void
		{
			event.preventDefault();
			updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;;
		}

		private function downloadStarted(event:UpdateEvent):void
		{
			updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
		}

		private function downloadProgress(event:ProgressEvent):void
		{
			updaterDialog.updateState = UpdaterDialog.UPDATE_DOWNLOADING;
			var num:Number = (event.bytesLoaded/event.bytesTotal)*100;
			updaterDialog.downloadProgress(num);
		}

		private function downloadComplete(event:UpdateEvent):void
		{
			event.preventDefault(); // prevent default install
			updaterDialog.updateState = UpdaterDialog.INSTALL_UPDATE;
		}

		private function downloadError(event:DownloadErrorEvent):void
		{
			event.preventDefault();
			updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
		}

		private function updaterError(event:ErrorEvent):void
		{
			updaterDialog.errorText = event.text;
			updaterDialog.updateState = UpdaterDialog.UPDATE_ERROR;
		}

		//----------  UpdaterDialog Events ----------------//

		private function createDialog(state:String):void
		{
			if(!updaterDialog) {
				updaterDialog = new UpdaterDialog();
				updaterDialog.isFirstRun = this.isFirstRun;
				updaterDialog.applicationName = this.applicationName;
				updaterDialog.installedVersion = this.installedVersion;
				updaterDialog.upateVersion = this.upateVersion;
				updaterDialog.updateState = state;
			 	updaterDialog.description = this.description;
				updaterDialog.addEventListener(UpdaterDialog.EVENT_CHECK_UPDATE, checkUpdate);
				updaterDialog.addEventListener(UpdaterDialog.EVENT_INSTALL_UPDATE, installUpdate);
				updaterDialog.addEventListener(UpdaterDialog.EVENT_CANCEL_UPDATE, cancelUpdate);
				updaterDialog.addEventListener(UpdaterDialog.EVENT_DOWNLOAD_UPDATE, downloadUpdate);
				updaterDialog.addEventListener(UpdaterDialog.EVENT_INSTALL_LATER, installLater);
				updaterDialog.open();
			}
		}

		/**
		 * Check for update.
		 * */
		private function checkUpdate(event:Event):void
		{
			//trace("checkUpdate");
			appUpdater.checkNow();
		}

		/**
		 * Install the update.
		 * */
		private function installUpdate(event:Event):void
		{
			appUpdater.installUpdate();
		}

		/**
		 * Install the update.
		 * */
		private function installLater(event:Event):void
		{
			isInstallPostponed = true;
			appUpdater.installUpdate();
			destoryUpdater();
		}

		/**
		 * Download the update.
		 * */
		private function downloadUpdate(event:Event):void
		{
			appUpdater.downloadUpdate();
		}

		/**
		 * Cancel the update.
		 * */
		private function cancelUpdate(event:Event):void
		{
			appUpdater.cancelUpdate();
			destoryUpdater();
		}

		//----------  Destroy All ----------------//

		private function destroy():void
		{
			if (appUpdater) {
				appUpdater.configurationFile = configurationFile;
				appUpdater.removeEventListener(UpdateEvent.INITIALIZED, updaterInitialized);
				appUpdater.removeEventListener(StatusUpdateEvent.UPDATE_STATUS, statusUpdate);
				appUpdater.removeEventListener(StatusUpdateErrorEvent.UPDATE_ERROR, statusUpdateError);
				appUpdater.removeEventListener(UpdateEvent.DOWNLOAD_START, downloadStarted);
				appUpdater.removeEventListener(ProgressEvent.PROGRESS, downloadProgress);
				appUpdater.removeEventListener(UpdateEvent.DOWNLOAD_COMPLETE, downloadComplete);
				appUpdater.removeEventListener(DownloadErrorEvent.DOWNLOAD_ERROR, downloadError);
				appUpdater.removeEventListener(UpdateEvent.BEFORE_INSTALL, beforeInstall);
				appUpdater.removeEventListener(ErrorEvent.ERROR, updaterError);


				appUpdater = null;
			}

			destoryUpdater();
		}

		private function destoryUpdater():void
		{
			if(updaterDialog) {
				updaterDialog.destroy();
				updaterDialog.removeEventListener(UpdaterDialog.EVENT_CHECK_UPDATE, checkUpdate);
				updaterDialog.removeEventListener(UpdaterDialog.EVENT_INSTALL_UPDATE, installUpdate);
				updaterDialog.removeEventListener(UpdaterDialog.EVENT_CANCEL_UPDATE, cancelUpdate);
				updaterDialog.removeEventListener(UpdaterDialog.EVENT_DOWNLOAD_UPDATE, downloadUpdate);
				updaterDialog.removeEventListener(UpdaterDialog.EVENT_INSTALL_LATER, installLater);
				updaterDialog.close();
				updaterDialog = null;
			}
			isInstallPostponed = false;
		}

		//----------  Utilities ----------------//

		/**
		 * Getter method to get the version of the application
		 * Based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
		 *
		 * @return String Version of application
		 *
		 */
		private function getApplicationVersion():String
		{
			var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
			var ns:Namespace = appXML.namespace();
			return appXML.ns::version;
		}

		/**
		 * Getter method to get the name of the application file
		 * Based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
		 *
		 * @return String name of application
		 *
		 */
		private function getApplicationFileName():String
		{
			var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
			var ns:Namespace = appXML.namespace();
			return appXML.ns::filename;
		}

		/**
		 * Getter method to get the name of the application, this does not support multi-language.
		 * Based on a method from Adobes ApplicationUpdaterDialogs.mxml, which is part of Adobes AIR Updater Framework
		 * Also based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
		 *
		 * @return String name of application
		 *
		 */
		private function getApplicationName():String
		{
			var applicationName:String;
			var xmlNS:Namespace=new Namespace("http://www.w3.org/XML/1998/namespace");
			var appXML:XML=NativeApplication.nativeApplication.applicationDescriptor;
			var ns:Namespace=appXML.namespace();

			// filename is mandatory
			var elem:XMLList=appXML.ns::filename;

			// use name is if it exists in the application descriptor
			if ((appXML.ns::name).length() != 0)
			{
				elem=appXML.ns::name;
			}

			// See if element contains simple content
			if (elem.hasSimpleContent())
			{
				applicationName=elem.toString();
			}

			return applicationName;
		}

		/**
		 * Helper method to get release notes, this does not support multi-language.
		 * Based on a method from Adobes ApplicationUpdaterDialogs.mxml, which is part of Adobes AIR Updater Framework
		 * Also based on Jens Krause blog post: http://www.websector.de/blog/2009/09/09/custom-applicationupdaterui-for-using-air-updater-framework-in-flex-4/
		 *
		 * @param detail Array of details
		 * @return String Release notes depending on locale chain
		 *
		 */
		protected function getUpdateDescription(details:Array):String
		{
			var text:String="";

			if (details.length == 1)
			{
				text=details[0][1];
			}
			return text;
		}
	}
}

UpdateDialog.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml" styleName="updateDialogWindow"
    layout="absolute" maximizable="false" resizable="false" currentState="{_updateState}"
    clipContent="false" showStatusBar="false" width="530" height="180">

    <mx:Metadata>
        [Event(name="checkUpdate", type="flash.events.Event")]
        [Event(name="downloadUpdate", type="flash.events.Event")]
        [Event(name="downloadUpdate", type="flash.events.Event")]
        [Event(name="cancelUpdate", type="flash.events.Event")]
    </mx:Metadata>

    <mx:Script>
        <![CDATA[
            public static var EVENT_CHECK_UPDATE:String = "checkUpdate";
            public static var EVENT_INSTALL_UPDATE:String = "installUpdate";
            public static var EVENT_DOWNLOAD_UPDATE:String = "downloadUpdate";
            public static var EVENT_CANCEL_UPDATE:String = "cancelUpdate";
            public static var EVENT_INSTALL_LATER:String = "installLater";

            [Bindable] public static var UPDATE_DOWNLOADING:String = "updateDownloading";
            [Bindable] public static var INSTALL_UPDATE:String = "installUpdate";
            [Bindable] public static var UPDATE_AVAILABLE:String = "updateAvailable";
            [Bindable] public static var NO_UPDATE:String = "noUpdate";
            [Bindable] public static var CHECK_UPDATE:String = "checkUpdate";
            [Bindable] public static var UPDATE_ERROR:String = "updateError";

            [Bindable] private var _isFirstRun:String;
            [Bindable] private var _installedVersion:String;
            [Bindable] private var _updateVersion:String;
            [Bindable] private var _updateDescription:String;
            [Bindable] private var _applicationName:String;
            [Bindable] private var _updateState:String;
            [Bindable] private var _errorText:String = "There was an error checking for updates.";

            public function set isFirstRun(value:String):void
            {
                _isFirstRun = value;
            }

            public function set installedVersion(value:String):void
            {
                _installedVersion = value;
            }

            public function set upateVersion(value:String):void
            {
                _updateVersion = value;
            }

            public function set updateState(value:String):void
            {
                _updateState = value;
            }

            public function set applicationName(value:String):void
            {
                _applicationName = value;
            }

            public function set description(value:String):void
            {
                _updateDescription = value;
            }

            public function set errorText(value:String):void
            {
                _errorText = value;
            }

            public function downloadProgress(value:Number):void
            {
                if(progressBar) progressBar.setProgress(value, 100);
            }

            private function continueUpdate():void
            {
                if (this.currentState == UpdaterDialog.CHECK_UPDATE){
                    this.dispatchEvent(new Event(EVENT_CHECK_UPDATE));
                } else if (this.currentState == UPDATE_AVAILABLE) {
                    this.dispatchEvent(new Event(EVENT_DOWNLOAD_UPDATE));
                }else if (this.currentState == INSTALL_UPDATE) {
                    this.dispatchEvent(new Event(EVENT_INSTALL_UPDATE));
                }
            }

            private function cancelUpdate():void
            {
                if (this.currentState == INSTALL_UPDATE) {
                    this.dispatchEvent(new Event(EVENT_INSTALL_LATER));
                    return;
                }
                this.dispatchEvent(new Event(EVENT_CANCEL_UPDATE));
            }

            public function destroy():void
            {
                iconImage.unloadAndStop(true);
                iconImage.source = null;

                continueButton.removeEventListener(MouseEvent.CLICK, continueUpdate);
                cancelButton.removeEventListener(MouseEvent.CLICK, cancelUpdate);

                // becaause we used skins, we have to clear them for garbage collection
                //http://www.firstrowria.com/2009/01/flex-top-5-memory-leaks-in-flex-2-skinning-of-components-eg-button/
                continueButton.styleName = null;
                cancelButton.styleName = null;

                while(this.numChildren > 0){
                    this.removeChildAt(0);
                }
            }
        ]]>
    </mx:Script>

    <mx:states>
        <mx:State name="{CHECK_UPDATE}">
            <mx:AddChild position="lastChild">
                <mx:Label x="152" y="86" text="Application:" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="230" y="86" text="{this._applicationName}" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="19" text="Check for updates" styleName="updateTitle" />
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="107" y="50" text="Allow the application to check for updates?"  styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:SetProperty name="height" value="180"/>
        </mx:State>
        <mx:State name="{UPDATE_AVAILABLE}">
            <mx:SetProperty name="height" value="360"/>
            <mx:AddChild position="lastChild">
                <mx:Text  text="{this._installedVersion}" x="230" y="114" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text  text="{this._updateVersion}" x="230" y="134" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="118" y="114" text="Installed Version:" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="127" y="134" text="Update Version:" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="152" y="96" text="Application:"  styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="230" y="96" text="{this._applicationName}" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="19" text="Update Available" styleName="updateTitle"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="107" y="50" text="An updated version of the application is available for download." styleName="updateDialogText"/>
            </mx:AddChild>

            <mx:AddChild position="lastChild">
                <mx:Label id="releaseLabel" x="10" y="222" text="Release notes" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:TextArea text="{_updateDescription}" x="10" y="248" width="508" height="100" styleName="updateDialogTextArea"/>
            </mx:AddChild>

            <mx:AddChild position="lastChild">
                <mx:HRule x="10" y="214" width="508" styleName="updateDialogHRule"/>
            </mx:AddChild>
            <mx:SetProperty target="{cancelButton}" name="y" value="164"/>
            <mx:SetProperty target="{continueButton}" name="y" value="164"/>
            <mx:SetProperty target="{cancelButton}" name="label" value="Download later"/>
            <mx:SetProperty target="{continueButton}" name="x" value="269"/>
            <mx:SetProperty target="{continueButton}" name="label" value="Download now"/>
        </mx:State>
        <mx:State name="{NO_UPDATE}">
            <mx:AddChild position="lastChild">
                <mx:Label x="122" y="86" text="Application:" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="200" y="86" text="{this._applicationName}" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="19" text="No Updates" styleName="updateTitle"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="107" y="50" text="There is no application update available at this time." styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:RemoveChild target="{continueButton}"/>
            <mx:SetProperty target="{cancelButton}" name="label" value="Close"/>
            <mx:SetProperty name="height" value="180"/>
        </mx:State>
        <mx:State name="{UPDATE_DOWNLOADING}">
            <mx:AddChild position="lastChild">
                <mx:ProgressBar x="107" y="84" width="411" label=" " id="progressBar" mode="manual" height="15" styleName="updateDiallogProgress"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="107" y="20" text="Downloading Update" styleName="updateTitle"/>
            </mx:AddChild>
            <mx:RemoveChild target="{continueButton}"/>
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="53" text="Download progress..."  styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:SetProperty name="height" value="180"/>
        </mx:State>
        <mx:State name="{UPDATE_ERROR}">
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="19" text="Unexpected error" styleName="updateTitle"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="50" text="{_errorText}" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:RemoveChild target="{continueButton}"/>
            <mx:SetProperty target="{cancelButton}" name="label" value="Close"/>
            <mx:SetProperty name="height" value="180"/>
        </mx:State>
        <mx:State name="{INSTALL_UPDATE}">
            <mx:AddChild position="lastChild">
                <mx:Text  text="{this._installedVersion}" x="230" y="139" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text  text="{this._updateVersion}" x="230" y="159" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="118" y="139" text="Installed Version:" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="127" y="159" text="Update Version:"  styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="152" y="121" text="Application:" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="230" y="121" text="{this._applicationName}" styleName="updateDialogText"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Label x="107" y="19" text="Install update" id="windowTitle4" styleName="updateTitle"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:Text x="107" y="50" text="The update for the application is downloaded and ready to be installed." styleName="updateDialogText"/>
            </mx:AddChild>

            <mx:AddChild position="lastChild">
                <mx:Label x="10" y="262" text="Release notes" styleName="updateDialogLabel"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:TextArea id="relaeseNotesTextArea0" text="{_updateDescription}" x="10" y="288" width="508" height="100" styleName="updateDialogTextArea"/>
            </mx:AddChild>
            <mx:SetProperty name="height" value="402"/>
            <mx:AddChild position="lastChild">
                <mx:ProgressBar id="installProgressBar" x="107" y="84" width="411" label=" " mode="manual" height="15" styleName="installDiallogProgress" creationComplete="installProgressBar.setProgress(100,100)"/>
            </mx:AddChild>
            <mx:AddChild position="lastChild">
                <mx:HRule x="10" y="249" width="508" styleName="updateDialogHRule"/>
            </mx:AddChild>
            <mx:SetProperty target="{cancelButton}" name="label" value="Postpone until restart"/>
            <mx:SetProperty target="{cancelButton}" name="y" value="198"/>
            <mx:SetProperty target="{continueButton}" name="y" value="198"/>
            <mx:SetProperty target="{continueButton}" name="x" value="320"/>
            <mx:SetProperty target="{continueButton}" name="label" value="Install update"/>
        </mx:State>
    </mx:states>

    <mx:Button x="107" y="129" label="Cancel" id="cancelButton" click="cancelUpdate()" height="34" styleName="updateDialogButton"/>
    <mx:Button x="202" y="129" label="Check for Updates" id="continueButton" click="continueUpdate()" height="34" styleName="updateDialogButton"/>
    <mx:Image source="@Embed('/assets/images/UpdateIcon.png')" x="15" y="25" width="81" height="74" id="iconImage"/>

</mx:Window>

Main.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical" width="428" height="280" creationComplete="init()">

    <mx:Style source="assets/styles.css"/>

    <mx:Script>
        <![CDATA[
            import com.thanksmister.UpdateManager;
            import mx.rpc.events.ResultEvent;

            private var updater:UpdateManager;
            [Bindable] private var baseURL:String;
            [Bindable] private var updates:String;

            private function init():void
            {
                configService.send();
            }

            private function handleResult(event:ResultEvent):void
            {
                var xml:XML = event.result as XML;
                baseURL = xml..baseurl.toString();
                updates = xml..updates.toString();

                if(updates)
                    updater = new UpdateManager(true, false);
            }
        ]]>
    </mx:Script>

    <mx:HTTPService id="configService" method="GET" resultFormat="e4x" url="config/configuration.xml" result="handleResult(event)" />

    <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"/>

    <mx:Button label="Begin update process" color="0xFFFFFFF"  x="125" y="104" click="updater.checkNow()"/>

    <mx:Label id="versionText" fontSize="14" color="0xFFFFFFF"/>
    <mx:Label text="{'Base URL: ' + baseURL}" color="0xFFFFFFF"/>
    <mx:Label text="{'Updates: ' + updates}" color="0xFFFFFFF"/>
</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):

private function statusUpdate(event:StatusUpdateEvent):void
		{
			//trace("statusUpdate");
			 event.preventDefault();
			 if(event.available){
			 	this.description = getUpdateDescription(event.details);
			 	this.upateVersion = event.version;

			 	if(!showCheckState) {
			 		createDialog(UpdaterDialog.UPDATE_AVAILABLE);
			 	} else if (updaterDialog) {
			 		updaterDialog.applicationName = this.applicationName;
					updaterDialog.installedVersion = this.installedVersion;
					updaterDialog.upateVersion = this.upateVersion;
			 		updaterDialog.description = this.description
			 		updaterDialog.updateState = UpdaterDialog.UPDATE_AVAILABLE;
			 	}
			 } else {
			 	if (showCheckState) createDialog(UpdaterDialog.NO_UPDATE);
			 }
		}

-Mister

Spark TextFlow LinkElement Rollover

This is a little example of how to create a skinned rollover popup on a LinkElement object in Flash Builder 4 (Flex 4) within a TextFlow object. This is something I have been waiting for since 2001. The old way to create a rollover action on an HTML link was to create an invisible MovieClip that floats over your hyperlink. Flex Builder 4 greatly simplifies this by adding rollover, mouse, and click events to all hyperlinks within the TextFlow object. This is a HUGE plus

I borrowed the click event action from an example by Peter deHaan posted on his blog Flex Examples. You will need the latest build of the Flex Builder 4 SDK and some helpful instructions on how to install the latest Flex Builder 4 SDK.

Example AIR Application Output

LinkElement Rollover Example

Main.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication title="Spark RichEditableText LinkElement Rollover"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/halo">

    <fx:Script>
        <![CDATA[
            import flashx.textLayout.formats.TextDecoration;
            import mx.managers.PopUpManager;

            import flashx.textLayout.elements.LinkElement;
            import flashx.textLayout.events.FlowElementMouseEvent;
            import mx.controls.Alert;

            private var customToolTip:CustomToolTip;

            protected function clickHandler(evt:FlowElementMouseEvent):void {
                var linkEl:LinkElement = evt.flowElement as LinkElement;
                Alert.show("The '" + linkEl.getFirstLeaf().text + "' link would have gone to " + linkEl.href + " in a " + linkEl.target + " window, but it was cancelled.", "Fun with hyperlinks");
                evt.stopImmediatePropagation();
                evt.preventDefault();
            }

            protected function rollOverHandler(evt:FlowElementMouseEvent):void
            {
                if(!customToolTip){
                    customToolTip = new CustomToolTip();
                    customToolTip.x = this.mouseX - customToolTip.width/2;
                    customToolTip.y = this.mouseY - 40;
                    PopUpManager.addPopUp(customToolTip, this, false);
                }
                customToolTip.text =  LinkElement(evt.flowElement).href;
            }

            protected function rollOutHandler(evt:FlowElementMouseEvent):void
            {
               PopUpManager.removePopUp(customToolTip);
               customToolTip = null;

            }
        ]]>
    </fx:Script>

    <s:RichEditableText id="richEdTxt"  editable="false" selectable="false" focusEnabled="false" horizontalCenter="0" verticalCenter="0">
        <s:textFlow>
            <s:TextFlow>
                <!--
                <s:linkNormalFormat  textDecoration="{TextDecoration.NONE}"/>
                <s:linkHoverFormat textDecoration="{TextDecoration.UNDERLINE}" />
                <s:linkActiveFormat  textDecoration="{TextDecoration.UNDERLINE}" />
                -->
                <s:p>Text that includes a link to <s:a href="http://adobe.com/" target="_blank"
                    rollOver="rollOverHandler(event);" rollOut="rollOutHandler(event);"
                    click="clickHandler(event);">Adobe</s:a>.</s:p>
            </s:TextFlow>
        </s:textFlow>
    </s:RichEditableText>

</s:WindowedApplication>

CustomToolTip

<?xml version="1.0" encoding="utf-8"?>
<s:SkinnableContainer xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/halo">
    <fx:Script>
        <![CDATA[
            import spark.skins.spark.BorderSkin;

            [Bindable] private var _text:String;

            public function set text(value:String):void
            {
                _text = value;
            }
        ]]>
    </fx:Script>

    <s:Skin minHeight="25">

        <s:Rect id="upFill"
                top="1"
                right="1"
                left="1"
                bottom="1"
                radiusX="10"
                radiusY="10">
            <s:fill>
                <s:LinearGradient rotation="90">
                    <s:GradientEntry color="#222222"  ratio="0" alpha="0.45"/>
                    <s:GradientEntry color="#222222"  ratio="0.45"/>
                    <s:GradientEntry color="#222222"  ratio="0.65"/>
                    <s:GradientEntry color="#222222" ratio=".8"/>
                </s:LinearGradient>
            </s:fill>
            <s:stroke>
                <s:SolidColorStroke color="#222222" weight="0.5"/>
            </s:stroke>
        </s:Rect>

        <s:Rect id="highlightFill"
                top="2"
                right="2"
                left="2"
                bottom="2"
                radiusX="10"
                radiusY="10">
            <s:stroke>
                <s:LinearGradientStroke rotation="90" weight="1">
                    <s:GradientEntry color="#FDFDFD" ratio="0" alpha="0.6"/>
                    <s:GradientEntry color="#FDFDFD" ratio="0.2" alpha="0"/>
                </s:LinearGradientStroke>

            </s:stroke>
        </s:Rect>

        <s:SimpleText id="labelElement" text="{_text}"
                      color="#FFFFFF"
                      right="20"
                      left="20"
                      verticalAlign="middle"
                      horizontalCenter="0"
                      verticalCenter="1"/>

        <s:filters>
            <s:DropShadowFilter color="#999999"
                                blurX="5"
                                blurY="5"
                                angle="90"
                                distance="2"
                                alpha="0.8"/>
        </s:filters>
    </s:Skin>

</s:SkinnableContainer>

The only think I couldn’t get working for this example (the commented out section) was changing the format of the hyperlinks within the TextFlow object.

Source File

Additional Resources

http://labs.adobe.com/technologies/flashbuilder4/
http://livedocs.adobe.com/flex/gumbo/langref/spark/primitives/RichEditableText.html
http://blog.flexexamples.com/2009/08/27/creating-a-linkelement-in-a-spark-richeditabletext-control-in-flex-4/
http://blog.flexexamples.com/2009/10/21/customizing-the-appearance-or-a-hyperlink-in-a-textflow-object-in-flex-4/

http://blog.flexexamples.com/2009/07/13/downloading-and-installing-flex-4-sdk-builds-from-opensource-adobe-com-flash-builder-4-beta-edition/

-Mister

Flex Garbage collection and styles

I read a really great post in my endless search for better garbage collection in AIR and Flex. Bernd Bindreiter of firstrow RIA posted a 5-part series on garbage collection and one of them involves removing skins from Button controls in order to have them garbage collected properly. In his post he removes each individual skin from the button using something like:

myButton.setStyle("overSkin", null);
myButton.setStyle("upSkin", null);
myButton.setStyle("downSkin", null);

What I wondered was if I could do the same thing by just setting the styleName to null:

myButton.sytleName = null;

This does work for skins, but as Bernd’s comments point out, other styles such as “color, fontSize, paddingTop” don’t create memory leaks, so this method only works with skins. However, setting the styleName to null is also an effective way for marking the object ready to be garbage collected. I recommend reading the rest of his posts about garbage collection techniques. I am always searching for the holy grail of garbage collection and his posts were very insightful.

-Mister

Adobe MAX 2009 – Los Angeles

This year Adobe Max 2009 is in my own backyard, well sort of 6 miles up the street, which means about an hour away in LA traffic. I am hoping to get in for free some how, either through Adobe Max Awards or by posting the Adobe Max Widget. See everyone there, maybe…

http://max.adobe.com/widget/MaxWidget.swf