Adding Mac OS X Close Behavior to AIR Application

Just a quick post because I have seen this done on other sites but it no longer is valid in AIR 1.5. Most Mac OS X applications minimize to system tray on closing the application using the “x” close button on the app. To achieve this in AIR 1.5 you will need to do the following on creationComplete event of the main application:

 // on application creation complete setup default Mac OS X behavior
 private function init():void
 {
 if (NativeApplication.supportsMenu) { // we are on a Mac
 //this.nativeWindow.addEventListener(InvokeEvent.INVOKE, handleAppInvoke);
 NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, handleAppInvoke);
 this.nativeWindow.addEventListener(Event.CLOSING, handleAppHide);
 NativeApplication.nativeApplication.addEventListener(Event.EXITING, handleAppExit);
 NativeApplication.nativeApplication.autoExit = false;
 } else { // we are on a Windows machine
 this.nativeWindow.addEventListener(Event.CLOSING, handleAppClosing);
 NativeApplication.nativeApplication.autoExit = true;
 }
 }

// active that application
 private function handleAppInvoke(event:InvokeEvent):void
 {
 this.nativeWindow.activate();
 }

// close all open windows
 private function handleAppClosing(event:Event):void
 {
 event.preventDefault();
 for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i >= 0; --i) {
 NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
 }
 }

// hide the application instead of closing it
 private function handleAppHide(event:Event):void
 {
 if( this.nativeWindow.visible ) { // if the window is visible the event behaviour is canceld and the window is hidden
 event.preventDefault();
 this.nativeWindow.visible = false;
 }
 }

// close all open windows, remove event listener for hiding application and close applicatin
 private function handleAppExit(event:Event):void
 {
 this.nativeWindow.removeEventListener(Event.CLOSING, handleAppHide);

for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i >= 0; --i) {
 NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
 }
 this.close();
 }
 

- Mister

Garbage Collection and Flex Event Listeners

On a recent AIR project I have become aware and subsequently obsessed with memory. My concerns have centered around garbage collection and event listeners, specifically how you clean up event listeners to allow Garbage Collection (GC) to clean up objects. Most DisplayObjects implement the EventDispatcher class that allows it to broadcast events. Trolling around the googleverse I found some interesting ways to insure that you break strong references to allow clean up or manually remove event listeners on your own to make your application more memory savvy.

When you use addEventListener() method and register a event listener to an object you create reference. By default any object that has a reference to a listener will keep that reference until it is removed using the removeEventListener() method. Strong references will not be cleaned up by Flash player GC and remain in memory until the application is closed or the world ends, whichever comes first. This makes for a horribly leaky AIR applications, especially those applications that may run for days on the user’s system without being restarted.

Use Additional Parameters of Listener

The addEventListener() method does have some additional (and seldom used) parameters to create a weak reference when adding the listener to an object. On of those parameters is “useWeakReference”, the 5th parameter of the method. To utilize this parameter you need to set it from false to true:

addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false);
.....

var button:Button = new Button()
button.addEventListener(MouseEvent.click, handleMouseClick, false, 0, true);

private function handleMouseClick(event:MouseEvent):void
{
 // handle the event
}

This will allow the event to be cleaned up at some point by Flash garbage collection. If you are like me and and find it annoying that the useWeakReference parameter of the addEventListener() method is false by default, you can can extend the object and make useWeakReference true by default, then use that object in your project:

package com.custom.controls
{
	import mx.controls.Button;

	public class CustomButton extends Button
	{
		public function CustomButton()
		{
			super();
		}

		override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=true):void
		{
			super.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}

	}
}

Explicitly Removing Listeners

Even though setting useWeakReference=true will most likely mark an object for clean up, to be even more certain you should explicitly remove the event listener with the removeEventListener() method:

private var button:Button;

private function init()
{
     var button:Button = new Button();
     button.addEventListener(MouseEvent.click, handleMouseClick, false, 0, <strong>true</strong>);
}

private function handleMouseClick(event:MouseEvent):void
{
   button.removeEventListener(handleMouseClick);
   button = new Button();
   // handle the event
}

Now you are certain that you have cleaned up and removed the listener manually and the object will be removed at some point during GC since there is no longer an explicit reference to the object.

Anonymous Functions

Another technique that comes in handy is to removing event listeners when using anonymous functions as event handlers. The trick to this techniques is the arguments.callee:

var button:Button = new Button()
button.addEventListner(MouseEvent.CLICK, function(event:MouseEvent):void {
      event.currentTarget.removeEventListener(event.type, arguments.callee);
});
 

Bummer

Now for the disappointment. If you create an event listener using MXML they are always going to have strong reference by default with no way to change useWeakReference and they make it difficult to use removeEventListener() method:

private function handleClickEvent(event:Event):void
{
     // handle event
}

<mx:Button click="handleClickEvent(event)"/>

So these objects will not be cleaned up when created in this way unless you have some clean up method that explicitly removes the event listeners or you add the event listeners to MXML object using ActionScript instead of binding.

So basically you need to add objects or listeners in ActionScript rather than use MXML when using the addEventListener() method, which sort of makes MXML less valuable in some respects because binding to objects and listening to events is what drives the framework.

Additional Links

For a great discussion on the topic, see Ted Patricks post and be sure to read all the comments as many of the examples were taken from discussion.

Garbage Collection Articles:

http://spreadingfunkyness.com/garbage-collection-with-flex-and-adobe-air/
http://blogs.adobe.com/aharui/2007/03/garbage_collection_and_memory.html
http://gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
http://www.adobe.com/devnet/flashplayer/articles/resource_management.html

Adobe AIR NativeMenu issue on Mac

I think the NativeMenu is a great option for AIR applications as you can get a consistent look for the application that matches the operating system. The other advantage is that NativeMenu’s are “outside” of the application and do not get clipped as they would if you just created a popup Canvas with a List control. However, I could not get the native menu to appear as a popup in my tests. In fact, it would often result in crashing the AIR application when i did NativeMenu.display();.

If I call a function that creates a NativeMenu from a button click everything works great. If I call the same function to create the menu from an event (like receiving data from a service call), the menu is not created and the application crashes upon subsequent attempts to create the menu. In addition, trying to create a NativeMenu popup on “createComplete” of the application also causes a crash.

So let’s say you have an application that you want to show a NativeMenu as a popup on startup of the application so you run the following:

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

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

    <mx:Script>
        <![CDATA[
            private function init():void
            {
                var nativeMenu:NativeMenu = new NativeMenu();
                var menuItem1:NativeMenuItem = new NativeMenuItem("Menu Item 1");
                var menuItem2:NativeMenuItem = new NativeMenuItem("Menu Item 2");
                nativeMenu.addItem(menuItem1);
                nativeMenu.addItem(menuItem2);

                NativeApplication.nativeApplication.menu.display(this.stage, 100, 100);

            }
        ]]>
    </mx:Script>


</mx:WindowedApplication>

This will cause an immediate crash of the application. But if you create an non-poppup NativeMenu, it works:

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

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

    <mx:Script>
        <![CDATA[
            private function init():void
            {
                var nativeMenu:NativeMenu = new NativeMenu();
                var menuItem1:NativeMenuItem = new NativeMenuItem("Menu Item 1");
                var menuItem2:NativeMenuItem = new NativeMenuItem("Menu Item 2");
                nativeMenu.addItem(menuItem1);
                nativeMenu.addItem(menuItem2);

                if(NativeApplication.supportsMenu){
                    var menuItem:NativeMenuItem = new NativeMenuItem("Menu Name");
                    menuItem.submenu = nativeMenu;
                    NativeApplication.nativeApplication.menu.addItem(menuItem);
                }

            }
        ]]>
    </mx:Script>


</mx:WindowedApplication>

This works just fine. Initially I assumed this is some kind of timing issue or a possible undocumented bug. After a ton of research and much wasted time, I did find a possible bug mentioned by Marco Casario in his book “The Essential Guide to Flash CS4 AIR Development book is oriented to Flash developers “. Here is a snippet from his chapter on creating NatveMenu’s:

The display() method of the NativeMenu class only works properly on Microsoft Windows systems. Flash Player doesn’t work properly on Mac OS X systems, and displays the pop- up menu on the cursor of the mouse instead of at the specified coordinates. This occurs if the call to the display() function happens as a consequence of an event of the MouseEvent class . If the call happens in response to other events, the application doesn’t display the pop- up menu at all.

This limitation was not mentioned in the Adobe Documentation and as it happens, I was trying to create a NativeMenu popup for my AIR application on a Mac. I hate when you find these things out the hard way and waste a bunch of time tracking down obscure undocumented issues. If this is a limitation, it should clearly be presented as such when you look at the examples in the Adobe help. I think this is a Flash player on Mac issue, but so far I can’t find any official documentation on the problem nor could I track down an actual bug report. If anyone has any suggestions, please pass them on, thanks!

-Mr

Placing a ContextMenu on TextArea in AIR 1.5

The TextArea in Flex comes with its own contextual menu, so adding one does absolutely nothing but leave you wondering what the heck you are doing wrong. In the bug report there is a bit of a work around for AIR 1.5 which requires you to access the “textField” property of the TextArea and add your contextMenu to that item. Apparently there are not enough people using custom contextMenu’s to justify fixing the bug. The problem gets a bit trickier as the textField is protected in Flex Builder 3, so you can’t just do:

myTextArea.textfield.contextMenu = myMenu;

You need to expose or get at the textField to add the menu. You can do this by using mx_internal (careful, kid, or you’ll put your eye out with that thing!). So you can throw a little hack together to get your context menu on your text area:

private function createMenu():void
{
     var mainMenu:ContextMenu = new ContextMenu();

     var menuitem:ContextMenuItem = new ContextMenuItem("hello mom!");
     menuitem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handleContextMenuEvent);
     mainMenu.addItem(menuitem);

     textInput.contextMenu = mainMenu;

     var txt:TextField = textInput.mx_internal::getTextField() as TextField;
     txt.contextMenu = mainMenu;
}

private function handleContextMenuEvent(event:ContextMenuEvent):void
{
    var menuitem:ContextMenuItem = event.target as ContextMenuItem;
    trace("context menu event: " + menuitem);
}

<mx:TextArea id="textInput"  width="100%" height="100%">

Notice that I added the contextMenu to two spots, the textField inside the TextArea, and the TextArea itself, this will show only the “hello mom!”. If you wanted to just add “hello mom!” to the existing menu options of the TextArea (copy/paste/select all), then you can just add the menu to the textField only.

I thought I might be able to accomplish the same behavior using an AIR NativeMenu/NativeMenuItem. Using a NativeMenu instead of a ContextMenu item on a TextArea throws this error:

ReferenceError: Error #1069: Property clipboardItems not found on flash.display.NativeMenu and there is no default value.
at flash.display::InteractiveObject/onContextMenuEvent()

The workaround would then be to create a NativeMenu popup and position on the cursor position of the TextArea.

private function createMenu():void
{
     var mainMenu:NativeMenu = new NativeMenu();

     var menuitem:NativeMenuItem = new NativeMenuItem("hello mom!");
     menuitem.addEventListener(Event.DISPLAYING, handleDisplaying);
     mainMenu.addItem(menuitem);

     mainMenu.display(NativeApplication.nativeApplication.activeWindow.stage, 20, 20);
}

private function handleDisplaying(event:Event):void
{
    trace("native menu displayed");
}

-Mr

Dude, where's my phone (GPS)

I thought I had lost my phone for the past day, I looked every where and called it a few times, no luck. So I remembered I had the InstaMapper GPS software installed. You can send an SMS to the phone with your InstaMapper key to activate the GPS and have it report its location. Worked perfectly, apparently the phone is at my house some place hanging out without me, nice. I think its pretty cool that you can activate applications this way with the G1 phone.

Since iPhones don’t allow applications to run in the background, it wouldn’t have been possible, just another lost phone ringing with nobody to hear its plea to be found. I recommend anyone with a G1 to use InstaMapper. I also created an AIR application that tracks your device with Google Maps.

-Mr

HTTP Status 201 causing Flex #2032 Error in IE only

I ran into a problem right when we were going to launch a new Flex web-based application to production (of course) with Internet Explorer and Flex. I was calling an RESTful service that returns XML and using the built-in Flex HTTPService with either POST/GET as the method for the request. Most of the service calls either return a HTTP Status Code of 200 (OK) or they throw an error. For one POST request that returned a HTTP Status of “201 Created” Flex threw up a 2032 Stream Error in response. Here is a brief definition of the error:

The referenced resource does not exist or the swf is trying to access a file across a restricted domain.

This error causes Flex to report a fault error on the request even though it was successful. The 201 status code is handled fine in all other browsers except IE (6 & 7). I did some research on the issue thinking it must be something easy to fix, that many others have run across before me. It seems the error is out there for lots of different reasons.

One post reported that IE was having a caching issues, and the way to solve it was to add a random number on the end of your service calls or you have to set the response headers in your server side page to prevent caching. This one sounds similar to a FireFox issue I ran into a couple years back, but didn’t effect the results:

http://www.judahfrangipane.com/blog/?p=87

The overwhelming number of posts on the subject suggested that Rails and Adobe both share the responsibility for this error (depending on if you are a Rails dev or a Flex dev, the debate rages as to who is to blame). The fix seems to be changing status returned from Rails to something other than 201. This seems to be the legit solution since the web service I was hitting is running Rails:

http://stackoverflow.com/questions/354936/flex-2032-stream-error-in-ie-only
http://nachbar.name/blog/2008/06/14/flex-railsprotect_from_forgery-problem-with-rails-21-produces-ioerror-2032/
http://onrails.org/articles/2008/02/20/dealing-with-http-errors-in-a-flex-with-rails-application
http://www.manning-sandbox.com/message.jspa?messageID=64372
http://nachbar.name/blog/tag/adobe-flex-programming/

If you think Adobe should fix the issue (as some Rails people protest) then you can view all the other bug reports Adobe already had lined up to fix that involve a 2032 Stream Error, just type in Explorer 2032 and you will find them:

http://bugs.adobe.com/jira/secure/QuickSearch.jspa

The workaround we ended up implemented was totally client based. In the fault handler of this particular service request, I look for the errorID value of the Fault event and ignore it. It was all I could do on short notice and I wasn’t about to convince the backend dudes that we needed to change the server response from 201 to 200, pick your battles and my first battle was to get this app into the war.

For me the real culprit is IE (easy target). Something that works in every other browser but IE is so friggen familiar to anyone who has done any cross-browser work. IE sucks, it has always sucked, and it will always suck, period. My other thought is about web services and codes in general. As an application developer, I use a lot of different services, consuming data from many different pipes. I couldn’t tell you as a consumer of web services and an application designer what the value of having a returned Status of 201 has over returning a Status of 200, can you? I make a request, and if the request is good, just tell me ok, I don’t need to know that the server returned a HTTP Status Code of 311 (the server code for “I just made you a sandwich”). I need to know only two basic things, was the request good, or did it explode?

I think some API dudes and backend dudes are purists, they want server codes to bubble up to the client so we can admire them, even though in practice (meaning you actually build applications with these services) you don’t really need the extra data. Hey, I am totally used to having to make 5 service calls to accomplish one thing with a web service, I would never ask for the service to be changed to convenience the client, but some information is superfluous and could be scaled down.

UPDATE

Saw another blog post about the same topic at UserFlex.

- Mister

Smooth Scrolling HorizontalList

This example is based on Alex Harui’s Smooth Scrolling List. It just changes the code to work with the HorizontalList control rather than the List control. The SmoothHorizontalScrollingList class extends the HorizontalList control adding the needed functionality to allow smooth scrolling:

Read more of this post

PhoneSeek an Adobe AIR application to track GPS enabled mobile devices

PhoneSeek is an application to track any device that uses InstaMapper. InstaMapper is free real-time GPS tracking service. To use InstaMapper you need a GPS enabled device with the free InstaMapper tracking software installed along with a API Key from InstaMapper’s web site for each device you own. You can track mobile phones (iPhone, G1, Blackberry) or even automobiles.

Use Case

With PhoneSeek I can add my InstaMapper API Key for my phone and track it from the desktop. I can also add and track multiple devices. On my G1 phone, I installed the Android version of the InstaMapper tracking software. This software allows me to send my phone’s GPS data to InstaMapper service where I can view it either on their website or consume the data through InstaMapper’s web service. In PhoneSeek I just add the API Key to begin tracking the advice as soon as your phone starts to transmit GPS data.

Get PhoneSeek

Download the latest version from this location.

Development Background

PhoneSeek might be used to track devices from your family members, or maybe track down a lost or stolen phone. However, Phoneseek was basically created as a test project to learn PureMVC and also experiment with the new Google Maps API SWC for Adobe AIR. Previous versions of the Google Maps API SWC only worked for web-based applications.

Coming from Cairngorm to PureMVC was quite an experience. At first it was really painful because I had a tendency to over think things, or try to structure things more the Cairngorm ways (like dude, where are my delegates and commands).

I really had a hard time understanding where to put certain code, like update code, or code to set the window location on startup, and even how to pass around value objects used by more than one view. It took some time to get used to the PureMVC way, but all in all, its not a bad framework to work with. However, I still prefer Cairngorm and have since started to investigate and really enjoy Mate.

Credit for some of the graphics goes to DragonArtz Designs.

- Mister

Adobe AIR Runtime Font Differences

This was a heck of a problem to discover and even worse to try to figure out why. Sadly, I did not find a good solution the problem. I will give the short and sweet rundown of the issue. You have three different machines, three different operating system, the same version of Flex Builder (3.0.2), and the same code base. Now you would think that when you create a release version of an AIR application on one machine, then deploy it on another, you would get consistent results. However, we experienced that an AIR file created one Windows environment was different from the AIR file created on another Windows environment and a Mac machine. The difference was visible with the fonts displayed in the application.

I have to be clear about this part, because first you might think that the fonts are rendered differently on each machine because we didn’t embed the font within the application. So you would see different fonts on a Mac or from one PC to another. That would be a good guess, but you would be wrong. I have running on my Mac three copies of our AIR application (app_1.air, app_2.air, app_3.air), running side-by-side at the same time on the same screen. All three applications compiled form the same exact code base, but from different machines (two PC’s and one Mac).

One version of the application shows clearly readable fonts, the others two applicaions show thinner more jaggy fonts with some color differences. The most significant differences are displayed with italic text and text on fonts. I couldn’t believe it myself, but I have a screen shot to prove it. Below you can see all three applications. The screen shot was taken of the applications side-by-side and then changed in Photoshop for better display on the blog page (vertical instead of horizontal). You will notice that one has better looking text, thicker, richer colors, and just more readable. The other two are crap, well, not as readable, especially the button text and the italic text.

Image

The middle application clearly has the best displayed fonts, even though they are all running on the same exact machine, there is noticeable display differences. The only difference is the machine that compiled each AIR file. The font on the tabs is thicker for the middle one, the button fonts are clearer and thicker for the middle one, and the italic font is more readable. I don’t know how to fix this issue, embedding fonts did not work, installing the same fonts on each machine didn’t work, nothing helped! Why do some machines produce a better quality AIR file while others produce jaggy text? I couldn’t find any information on this problem, so please let me know if anyone has any solution.

UPDATE

If you run your AIR package on a Windows machine with “clear type” turned on, then it actually produces better fonts. This makes all three applications look identical on a PC, but with clear type off, then only one of the applications looks good. The application generated on the Mac, and generated on the PC with clear Type on, still look the degraded when compared to the middle application.

The best solution I could find to have a consistent look for the AIR application when created from different computers is to embed fonts within the application. Still does not solve the reason why there was a difference, but it does offer a temporary solution. For more information on embedding fonts check out Adobe docs on the topic.

- Mister

ImageCache, a cheap way to cache images in Adobe Flex

In a previous post about Ely’s SuperImage, I mentioned that we decided to implement a simpler method for caching images within a Flex application. ImageCache is a simple class file that extends the Image control by adding the ability for the control to cache bitmap data. Unlike SuperImage, ImacheCache can handled SWF files, it just lacks all the bells and whistles of SuperImage, though image flicker is eliminated.

Using ImageCache

ImacheCache is just two class files. ImageCache.as which extends the Image control and ImageCachUtility.as which is a Singleton class that used by Imagecache to store and retrieve the BitmapData for cached images. Bitmap data is stored in a regular ArrayCollection object and retrieved by using the full path to the image (because image names can be duplicates but paths should be unique). Once the amount of cached images reaches the cache limit, the controls works on first in first out, dropping off older cached images as it caches new ones and staying within the cache limit. SWF fiels are not actually cached and will load as normal. You just use ImageCache like you would the Image control within your project:


<controls:ImageCache cacheLimit="200" id="image" source="{data.url}" width="100" height="100" complete="imageComplete()" ioError="imageError()" xmlns:controls="com.thanksmister.controls.*"/>

Cache Limit

ImageCache has a property called “cacheLimit” which tells the control how many images to cache. Because caching images can reduce the performance of your application. The larger the cache limit value, the more memory your application will use because it is storing all the Bitmap data in memory. If you want to reset the image cache, create an instance of the ImageCacheUtility and call the method “clear()”. ImageCache has been used and tested extensively for large media sites. Below is a screen shot of images from Flickr, the list on the left uses the standard Image control, the list on the right uses the ImageCache control. It seems that there may be some security issue when loading Flickr images without doing a proxying the images. Run the example locally or proxy Flickr images for best results.

Example

Code

You can download the code for the above project here. You will need your own Flickr API key to make the application work.

-Mister

Follow

Get every new post delivered to your Inbox.