AS3 Scrolling List for Android and iOS devices

I created a very simple AS3 list that works with the both Android and iOS devices. The project files include a Flash Professional project created with Flash Builder 4. You will need Adobe AIR for Android or the packager for iPhone to create naive create native iOS (iPhone, iPad) or Android.

If you only want to build for Android, then I recommend you check out Adobe Flash Builder Burrito and the Flex Hero SDK (or SDK 4.5). There is already a list control in the Flex Hero SDK (or SDK 4.5) for Android devices. However for iOS devices, you will need a scrolling list that works with AS3 and CS5 for packaging.

The list I created is an AS3 list that works for multiple devices, touch scrolls, and uses custom item renderers that detect user interaction. Here is an example AS3 project with the list in action. Just use your mouse like you would your finger on a mobile device to scroll the list and select items.



AS Scrolling List (click to view)

The list is suited best for smaller sets of data because the list does not recycle list items. But for most mobile applications you don’t normally have that many items to scroll. Adobe also recommends that you not use the drawing API in Flash because of its memory consumption on mobile devices. It would be better to create a MovieClip or Sprite in Flash and use that as the background of your item renderer. However, int this project I used the drawing API to change the selection color of the list item throwing all caution to the wind.

TouchList

The TouchList class creates the list, adds items and handles touch events dispatched by the item renderers. You might notice that I didn’t use any actual TouchEvent listeners in the list. This is because a TouchEvent is essentially a MouseEvent and I couldn’t see any difference in using one over the other. The TouchList class has a built in delay to differentiate between a scrolling and touch action. Like the Android phone, you can’t select an item while scrolling and pressed items are deselected if you scroll while pressed.

TouchListItemRenderer

TouchListItemRenderer implements ITouchListItemRenderer and renders the display of the items in the list data. This renderer can be customized to show whatever type of data you want in the list. List items can also be variable height.

ITouchListItemRenderer

If you want to create an item renderer for the list, then it must implement the ITouchListItemRenderer interface. This interface gives the renderer basic functionality to interact with user selection and touch events used by the list.

ListItemEvent

The list item event is a custom custom ListItemEvent dispached when a list item renderer is pressed. The event contains the event payload and a instance of the item renderer selected.

Installation

Included in the GitHub repository is the working project files for that I created in Flash Builder 4 that handles adding the list to the stage, screen orientation on the device, stage resize, and other functions for an Android AIR application. To install, just checkout the project file and import it into Flash Builder. I have also included Android .apk file if you want to deploy it directly to your Android phone.

The AS3ScrollinList project is located on GitHub.

If you do use the list in a project, be sure to drop me a note or mention me in your will. This list is actually a combination of my efforts and those of others in the Flash community. So please share what you build as well. If you have improvements, just post them back to this post or feel free to fork the GitHub code.

Work Cited

-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

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

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

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.
Continue reading

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

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

Storing an ArrayCollection in the EncryptedLocalStore

On one of my side AIR projects I got the idea to store an ArrayCollection (AC) object into the EncryptedLocalStore (ELS) to have a sort of mini data structure. I thought it would be no problem since you can basically throw anything you want into the EncryptedLocalStore as long as its not over 10MB where performance would be hampered. I built a little sample script to test the idea by filling an AC with a bunch of value objects. Upon retrieving the AC from the ELS, I cast it as an AC, but none of the value objects were readable. I tried casting the value objects, but that threw a runtime error in Flex.

I did some searching around and found one example from the Adobe AIR cookbook that stores and retrieves an value object from the ELS, casting the object as a value object when retrieved. However, I really wanted to store a whole AC of value objects and retrieve them. So I did a little more searching and found one blog post that outlines the same issue I was having: “when I read the object from disk back into my application, the type of objects from Array Collection is lost.”

Ok, cool, so the blog post lists a generic function to solve the solution by transforming the objects stored in the AC back to a proper value object. I wanted to make a little demo that combines the AIR cookbook example with the solution for storing/retrieving AC from the ELS with a slight modification of the generic function.

The first step is to create our Contact value object similar to the cookbook example:

Contact.as

package com.thanksmister
{
	[Bindable]
	public class Contact
	{
		public var firstName:String;
		public var lastName:String;
		public var phoneNumber:String;

		public function Contact(firstName:String = "", lastName:String = "", phoneNumber:String = "")
		{
			this.firstName = firstName;
			this.lastName = lastName;
			this.phoneNumber = phoneNumber;
		}

	}
}

Now we create a function that creates an ArrayCollection of Contact value objects and stores those in the EncryptedLocalStore:

private function storeContacts():void
{
     var contact:Contact = new Contact( "Michael", "Ritchie", "555-555-5555" );
     var ac:ArrayCollection = new ArrayCollection();
          ac.addItem(contact);
     var bytes:ByteArray = new ByteArray();
           bytes.writeObject( ac );
    EncryptedLocalStore.setItem( "contacts", bytes );
}

Ok, now we have stored out ArrayCollection of Contact VO’s in the EncryptedLocalStore, now we need to write a function that retrieves the AC and transforms all of the VO’s back to something that we can utilize within Flex:

private function retrieveContacts():void
{
    // retrieve AC from ELS
     var bytes:ByteArray = EncryptedLocalStore.getItem( "contacts" );
     var collection:ArrayCollection = bytes.readObject() as ArrayCollection;
     collection = serializeContacts(Contact, collection);
    // trace our collection value
    trace(Contact(collection.getItemAt(0)).firstName);
}

private function serializeContacts(valueObject:Class, arryCollection:ArrayCollection):ArrayCollection
{
     // use the the flash.utils.describeType to create an XML of the property names within the object
     var xml:XML = describeType(new valueObject());
     var properties:XMLList = xml..accessor.@name;
     var tempac:ArrayCollection = new ArrayCollection();

     // iterate through the collection passed into the function and reassign the types to it's objects
     for each (var object:Object in arryCollection) {
          var tempvo:Object = new valueObject();
           for each(var name:Object in properties) {
                tempvo[name] = object[name];
           }
           tempac.addItem(tempvo); //add the item to collection
      }

       // return an ArrayCollection of the Class file passed into the function
       return tempac;
}

The above serializeContacts function takes a Class file (VO) and an ArrayCollection pulled from the EncryptedLocalStore using the retrieveContacts function. The VO is our Contact VO that we want to use to transform our ArrayCollection of objects. This generic function is pretty much what Mihai Corlan used on his blog post, but I changed” xml..value.@name;” from the example he posted to “xml..accessor.@name;” in my example in this post because the dump using the describeType() utility method the node we want is “accessor”. (The describeType() method returns an xml dump of the details about the object that is passed into the method. This is a pretty cool function that I previously didn’t know about.) Here is the dump of our Contact value object when I trace out the “xml” value from the above serializeContacts function:

xml:

<type name="com.thanksmister::Contact" base="Object" isdynamic="false" isfinal="false" isstatic="false">
  <extendsclass type="Object">
  <implementsinterface type="flash.events::IEventDispatcher">
  <constructor>
    <parameter index="1" type="String" optional="true">
    <parameter index="2" type="String" optional="true">
    <parameter index="3" type="String" optional="true">
  </parameter>
  <accessor name="lastName" access="readwrite" type="String" declaredby="com.thanksmister::Contact">
    <metadata name="Bindable">
      <arg key="event" value="propertyChange">
    </arg>
  </metadata>
  <method name="dispatchEvent" declaredby="com.thanksmister::Contact" returntype="Boolean">
    <parameter index="1" type="flash.events::Event" optional="false">
  </parameter>
  <method name="hasEventListener" declaredby="com.thanksmister::Contact" returntype="Boolean">
    <parameter index="1" type="String" optional="false">
  </parameter>
  <accessor name="phoneNumber" access="readwrite" type="String" declaredby="com.thanksmister::Contact">
    <metadata name="Bindable">
      <arg key="event" value="propertyChange">
    </arg>
  </metadata>
  <method name="willTrigger" declaredby="com.thanksmister::Contact" returntype="Boolean">
    <parameter index="1" type="String" optional="false">
  </parameter>
  <method name="removeEventListener" declaredby="com.thanksmister::Contact" returntype="void">
    <parameter index="1" type="String" optional="false">
    <parameter index="2" type="Function" optional="false">
    <parameter index="3" type="Boolean" optional="true">
  </parameter>
  <accessor name="firstName" access="readwrite" type="String" declaredby="com.thanksmister::Contact">
    <metadata name="Bindable">
      <arg key="event" value="propertyChange">
    </arg>
  </metadata>
  <method name="addEventListener" declaredby="com.thanksmister::Contact" returntype="void">
    <parameter index="1" type="String" optional="false">
    <parameter index="2" type="Function" optional="false">
    <parameter index="3" type="Boolean" optional="true">
    <parameter index="4" type="int" optional="true">
    <parameter index="5" type="Boolean" optional="true">
  </parameter>
</parameter>

We are interested in the name property of accessor node, and we store those names in an XMLList called “properties”. This XMLList will be used later to assign the values of the Contact properties within the serializeContacts function. So this gives us a legitimate Contact value objects stored in the ArrayCollection, which we can then use in our application without any errors or null values.

Here is the complete sample application code that can be copied and used in an AIR application:

Main.mxml:

	<mx:windowedapplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="435" height="277">
    <mx:script>
        <!--[CDATA[
            import mx.collections.ArrayCollection;
            import com.thanksmister.Contact;
            import flash.utils.describeType;
            import flash.utils.ByteArray;

            private function storeContacts():void
            {
                var contact:Contact = new Contact( "Michael", "Ritchie", "555-555-5555" );
                var ac:ArrayCollection = new ArrayCollection();
                    ac.addItem(contact);

                var bytes:ByteArray = new ByteArray();
                    bytes.writeObject( ac );

                EncryptedLocalStore.setItem( "contacts", bytes );

                outPutText.text = ("Contact Stored\n");
            }

            private function retrieveContacts():void
            {
                // retrieve AC from ELS
                var bytes:ByteArray = EncryptedLocalStore.getItem( "contacts" );
                var collection:ArrayCollection = bytes.readObject() as ArrayCollection;
                    collection = serializeContacts(Contact, collection);

                // trace our collection value
                outPutText.text = ("First Name: " + Contact(collection.getItemAt(0)).firstName) + "\n";
                outPutText.text = ("Last Name: " + Contact(collection.getItemAt(0)).lastName) + "\n";
                outPutText.text = ("Phone: " + Contact(collection.getItemAt(0)).phoneNumber) + "\n";
            }

            private function serializeContacts(valueObject:Class, arryCollection:ArrayCollection):ArrayCollection
            {
                 // use the the flash.utils.describeType to create an XML of the property names within the object
                 var xml:XML = describeType(new valueObject());
                 //trace(xml.toString());
                 var properties:XMLList = xml..accessor.@name;
                 var tempac:ArrayCollection = new ArrayCollection();

                 // iterate through the collection passed into the function and reassign the types to it's objects
                 for each (var object:Object in arryCollection) {
                      var tempvo:Object = new valueObject();
                       for each(var name:Object in properties) {
                            tempvo[name] = object[name];
                       }
                       tempac.addItem(tempvo); //add the item to collection
                  }

                  // return an ArrayCollection of the Class file passed into the function
                  return tempac;
            }
    </mx:script>
    <mx:hbox width="300" x="10" y="10">
        <mx:button label="Store Contacts" click="storeContacts()">
        <mx:button label="Retrieve Contants" click="retrieveContacts()">
    </mx:button>
    <mx:textarea id="outPutText" width="400" height="200" x="10" y="40">
</mx:textarea>

Now we can store and retrieve ArrayCollections from the EncryptedLocalStore without dealing with the hosed object in our application. Many thanks to Daniel Dura and Mihai Corlan who helped tackle this problem in their contributions to the community. Sweet!

Update

I recently read a way to persist typed objects when storing them to the ELS, method outlined on Sönke Rohde’s blog.

-Mister