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

3 Comments

  1. Thanks for sharing!
    I found one thing in the serialize function that did not work for me since my VO contains readonly variables so E4X expression should look like this:
    var properties:XMLList = xml..accessor.(@access == ‘readwrite’).@name;

  2. Your listed air code is incomplete and does not work.

    It is missing the end of serialize function and app itself.

Comments are closed.