Migrating Expired Certificates using AIR 1.5.3

If you renew your certificate and you are running an earlier version of Adobe AIR than 1.5.3, you are in a world of hurt. The process of updating and renewing your certificate is enough of a pain, but trying to update your AIR application after your certificate expires prior to 1.5.3 required you to just uninstall your previous version and reinstall a new version of your application.

Adobe AIR 1.5.3 Update

Luckily with AIR 1.5.3 you now have a grace period of 6 months to renew your certificate and still be able to publish an update to your application that users can install over the previous application. The down side is that it’s is not easy to update your certificate once you update to 1.5.3. You can read more about the Adobe AIR 1.5.3 update and changes to certificate migration here:

Release notes for Adobe AIR developers
Adobe AIR 1.5.3 Now Available

Updating Flex Builder 3 for AIR 1.5.3

First off, grab and install the latest and greatest AIR 1.5.3 from Adobe’s download site. You will also need to download the Adobe AIR 1.5.3 SDK to install within your Flex Builder 3. If you just change the descriptor file and not update the SDK to use AIR 1.5.3, you will receive the following error when compiling the project from the IDE:

invalid application descriptor: descriptor version does not match runtime version

or

EncryptedLocalStore may not use publisher IDs passed in from ADL

The instructions for updating Flex Builder 3 to use AIR 1.5.3 are not really clear, at least not for me. The best way to upgrade on the MAC is to download the AIR 1.5.3 SDK and manually drag the files to your Flex Builder 3 SDK folder. When you download the AIR 1.5.3 SDK and expand it, you can see the following folder structure:

AIR SDK 1.5.3

You can just drag the folders one-by-one ( or the contents of the folder to be more careful ) to your Flex Builder 3 application directory inside the sdks folder. Now restart your Flex Builder 3 and create a new dummy project. If all goes well, your new project will have the 1.5.3 namespace already in the descriptor file. Now for existing Flex Builder 3 projects, you need to manually update your application’s namespace to 1.5.3 and add a new descriptor tag for publisherID (you don’t need to do this for a new AIR application, only if you are updating an project to AIR 1.5.3):

<!--?xml version="1.0" encoding="UTF-8"?-->
    <!-- The application identifier string, unique to this application. Required. -->
    MyApplication

    3782AD3EDB99182DA9E106898998986691F7E39C8DBA6A3.1
        ...

The publisherID is a combination of your application name and the publish id for the application. You can find the publisherID manually by looking at your currently installed application directory on MAC (Windows users can find it in the release notes):

MaciontoshHD > Users > [username] > Library > Application Support > Adobe > AIR > ELS > MyApplication.3782AD3EDB99182DA9E106898998986691F7E39C8DBA6A3.1

The long number after the application name is the publisherID, copy this and add it to your application descriptor file. You can also get the application publisherID from the NativeApplication:

trace(NativeApplication.nativeApplication.publisherID);

It should be noted that bye adding the …. to the descriptor file will create an error when compiling your application using Flex Builder 3:

invocation forwarded to primary instance

So this must be commented out of the descriptor file until you are ready to publish. If you leave this commented out when you publish the application, you won’t be able to migrate the application. So I am not sure how tracing the publisherID is going to be a help to you from the IDE when testing. You should also update the debug version of your Flash Player to the latest (10.0.42.34) before compiling your application. Now you can test your application from the IDE and it should work.

Migrating Expired Certificate

Now your application should run from the IDE and you can prepare to publish the application using your renewed certificate. Publish the application as normal using the renewed certificate to create the AIR compiled application. Now you will need to migrate your previous expired certificate using the ADT migration tools. The instructions for migrating certificates using the ADT migration tools can be found here:

http://www.insideria.com/2009/04/migrating-air-application-cert.html

Once you successfully migrated your previous expired certificate, your compiled AIR application is ready to update your previous version of the installed application on your system. If things didn’t go correctly, you will get an error that an application already exists with the same name, and you need to install the application in a different directory. That would be bad, and you probably missed one of the delicate steps and need to go through it all again. If you do get an update option to replace the existing application, you can no celebrate.

My Opinion about Certificates

In my opinion, using 3rd party certificate vendors (and paying for them) like Thawte, Verisign, ect. is total nonsense and just way to cumbersome. Adobe could easily provide a way to authenticate the authors of AIR applications with minimal cost to developers. This is especially important when you just want to publish an application for free and distribute it through the Adobe market place.

Adobe could borrow some ideas from Android Market, especially since Adobe plans on allowing developers to create more and more Flash/Flex content for mobile devices. Developers don’t need some complicated and expensive system in place to review code and authorize updates (i.e. iPhone app store), we simply need a way to verify to the end user that the author is who she says she is and the application is protected from someone else writing an application with identical name.

The renewal process for a certificate and the above steps to migrate certificates is a total pain, and I think an unnecessary barrier for Adobe AIR application developers. Anything Adobe could to do to lower the barrier for the average AIR developer to create certified applications would be greatly appreciated. There are also other ways to freely publish and distribute your application freely as described on this post at polyGeek.com:

http://polygeek.com/862_flex_deploying-trusted-air-apps-without-certification-for-free

-Mister

Preventing scrolling and mouse events on out of focus AIR application

This has been a little bit of annoyance for me for some time. When an AIR application (like any Twitter application) is out of focus and you click on the application activate it, you inadvertently click a link or button within the application. You are also able to scroll the application when the application has no focus, which may or may not be your desired behavior.

I wanted a way to prevent mouse events and scrolling until the application has focus. I tried to find away around this annoyance by capturing if the application has focus by listening for Event.ACTIVATE or Event.DEACTIVATE events on the NativeApplication.nativeApplication:

 NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, handleAppActivate);
 NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, handleAppDiactivate);

private function handleAppActivate(event:Event):void
 {
 // set a flag that application has focus so now links and buttons work
 }

private function handleAppDiactivate(event:Event):void
 {
 // set some flag to let the application has no focus so disable links and buttons
 }
 

This solution has a couple of problems. First the application has focus the instance you click on any link within the application window. So fine, the way around that is to use some kind of timer so that your flag that says application is now active is not set until some time after the application receives focus. This leads to the second problem, at least for larger scale applications. You have to pipe the flag that says the application has no focus to every button and link through the entire application, this is a huge pain.

Today I just happened to be playing with the new FramerateThrottler class created by Grant Skinner to reduce CPU usage for AIR applications running on Mac (this is yet another gripe of mine). So on his bog there are some comments about using mouseChildren and mouseEnabled on all open AIR windows to reduce CPU. Anyways, when I was messing around with this suggestion, I happened to notice my application no longer scrolled when the application was in the background, but mouse events were still active.

So I then thought of combining my previous attempt at preventing links and buttons from being clicked on with mouseChildren and mouseEnabled. I ended up only setting mouseChildren and mouseEnabled to true if the application has focus, and after a small timer dalay of 500 miliseconds. That gives me just enough time to click on the application any where without also clicking on a link or button and activating it. So now I can click on the application to give it focus any place I like without firing off click events:

 NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, handleAppActivate);
 NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, handleAppDiactivate);

private var timer:Timer;
 private function handleAppActivate(event:Event):void
 {
 // set timer to enable mouse events after short delay on app activation
 timer = new Timer(500);
 timer.addEventListener(TimerEvent.COMPLETE, enableAppEvents);
 timer.start();
 }

private function handleAppDiactivate(event:Event):void
 {
 this.mouseChildren = false;
 this.mouseEnabled = false;
 }

private function enableAppEvents(event:TimerEvent):void
 {
 timer.stop();
 timer.removeEventListener(TimerEvent.COMPLETE, enableAppEvents);

// now scrolling and mouse events work
 this.mouseChildren = true;
 this.mouseEnabled = true;
 }
 

That’s it, a simple solution that was probably sitting under my nose the whole time but I never thought of it.

UPDATE

Jonnie Hallman from Adobe posted a great article that expands the idea of handling throttling and events (mouse wheel for example).

http://www.adobe.com/devnet/air/flex/articles/framerate_throttling.html

- 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

Adding Application Menu Bar Items to AIR application

Just a quick how-to post on adding application menu bar items to your Adobe AIR application when running. Certain operating systems can support application menu bars, so this example will not be true for all systems. You can check this using “NativeApplication.supportsMenu == true”, which basically means you are on Mac OSX dude. If you were on a Windows or Linux box you would check for “NativeWindow.supportsMenu == true” and add the menu items to the NativeWindow rather than the NativeApplication.

As it turns out, I ran into just a small confusing snag that initially prevented the added application menu bar item from firing a select event. I eventually reached out to the Adobe AIR Tight discussion group and Jesse Warden had a quick fix. It turns out that if you do not use strong item references, they are swept away by Garbage Collection (GC) before you actually use the items. For example instead of creating the NativeMenuItem within the scope of the function, I had to create it within the scope of the class or in this case, the WindowedApplication.

In my application I only needed to add a single menu bar item. So instead of blowing away the entire application menu and then creating all my own menu items (as many of the examples show). I just wanted to add a single item to the exiting application menu bar. On Mac OS, you get 4 application menu items: YourAppName, File, Edit, and Window. YourAppName would be your applications name or if you are running in the Eclipse IDE, just simply “adl”. In my case I wanted to add a “Preferences” option under the YourAppName. You can do this by accessing the NativeMenuItem representing the YourAppName. Something like NativeApplication.nativeApplication.menu.items[0] will return a NativeMenuItem with that reference. Using that reference you can then add a new NativeMenuItem to the submenu of the referenced NativeMenuItem. In addition to adding items to the submenu, you can also detect when the new item is selected.

In any event, here is the complete application code example:


<?xml version="1.0" encoding="utf-8"?>
 <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" windowComplete="createApplicationMenu()"  >

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

     <mx:Script>
         <![CDATA[
                        import mx.controls.Alert;

                         private var prefItem:NativeMenuItem;
             private var menuItem:NativeMenuItem;

                        private function createApplicationMenu():void
             {
                 if(NativeApplication.supportsMenu) {
                     prefItem = new NativeMenuItem("Preferences...");
                     prefItem.addEventListener(Event.SELECT, handlePreferencesSelected);
                     prefItem.keyEquivalent = ",";

                     menuItem = NativeMenuItem(NativeApplication.nativeApplication.menu.items[0]);
                     menuItem.submenu.addItemAt(new NativeMenuItem("", true), 1);
                     menuItem.submenu.addItemAt(prefItem, 2);
                 }
             }

                        private function handlePreferencesSelected(event:Event):void
             {
                 Alert.show("handlePreferencesSelected");
             }

         ]]>
     </mx:Script>
 </mx:WindowedApplication>

That’s it, so now you can have a new item under your application name in the application menu bar to fire whatever function you desire.

-Mister

Adobe AIR TextField bug when resetting HTML text.

I ran into a really strange issue that only occurs within Adobe AIR applications (AIR 1.5.1/1.5.2). When I create a TextField control, set it’s style, and then assign it HTML text it appears fine. However, if I wish to reset that TextField content and the original content has links, the entire replaced content becomes one HTML link. I think the issue can be better explained with some images and some code samples.
Read more of this post

Truncating HTML Text

With Flex truncating text within a Label control is easily done by setting the property “truncateToFit” to true. This parameter doesn’t do much when using the Text or TextArea controls in Flex. To truncate text in these controls you would have to build your own function to count characters and add the ellipses. There is one good example of creating your own custom Text control that truncates the text in the same manner as the Label control based on size of the control.
Read more of this post

Using AIR 1.5.1 InovkeReason for friendlier applications

I recently discovered a new feature of AIR 1.5.1 that makes AIR applications more consumer friendly in certain situations. When you have your application set to start on user login using “NativeApplication.nativeApplication.startAtLogin = true”, you may want the application to start as a background process rather than show the entire application at login. In AIR 1.5.1 there is a new property of the InvokeEvent called “reason” that will report if the application was started at login or was started when the users invokes the application by launching the application.
Read more of this post

Follow

Get every new post delivered to your Inbox.