Adobe AIR and Flex - Saving Serialized Objects to File

Skill

Adobe AIR and Flex - Saving Serialized Objects to File

Posted in:

When creating any desktop application there is almost always a time when you need to store data on the computer. Now with Adobe AIR we have several options. One would be that we could use the built-in SQLite database support, for a small amount of data this is overkill. Another option is that we could turn the data into XML and write out to a file, the problem with this is that we we have to write some kind of decoder if we want a typed object. There is yet another option we have in Adobe AIR, we can serialize the object into a byte array and write it out to a file.

Ok, so we can write out an object to a file, what does this buy us? Well, two things really. First, as a byte array the data is going to be really small so we are saving space. Lastly, with a trick or two we can serialize typed objects and get back typed objects.

For this tutorial, we are going to save some user preference data at application close and read it back in the next time the application is opened. So let's take a look at the object we are going to save to file.

package
{
  [RemoteClass]
  public class UserPrefs
  {
    public var name:String;
    public var appPosX:Number;
    public var appPosY:Number;
    public var appWidth:Number;
    public var appHeight:Number;
  }
}

Well, nothing above should look out of the ordinary except maybe one thing - the [RemoteClass] metadata tag. Adobe has a pretty good description of the RemoteClass tag below.

Use the [RemoteClass] metadata tag to register the class with Flex so that Flex preserves type information when a class instance is serialized by using Action Message Format (AMF).

Basically, we need the tag to make sure that when we serialize the object it will keep its type (in this case UserPrefs) and then when we read the object back out it knows what type of object it is.

In order to save the objects out to file we just need a few lines of code. To handle this I created a class with a few static functions that can be used. The class, named FileSerializer, will have functions to write objects to file and read objects from file.

package com.paranoidferret.util
{
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
 
  public class FileSerializer
  {
    public static function writeObjectToFile(object:Object, fname:String):void
    {
      var file:File = File.applicationStorageDirectory.resolvePath(fname);

      var fileStream:FileStream = new FileStream();
      fileStream.open(file, FileMode.WRITE);
      fileStream.writeObject(object);
      fileStream.close();
    }
  }
}

Above you will notice that the function takes two parameters, object and fname. The first is the object to write out to file and second if the filename to use. the first line of the function gets a handle on the file we are going to write to. We first create a FileStream and open the file for writing. We then write the object out to the file using the function writeObject and then close the file stream. Yes, it is really that easy.

Now that we can write the object out to file we should also create a function to read it in. Below we have our complete FileSerializer class with our new read function added in.

package com.paranoidferret.util
{
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
 
  public class FileSerializer
  {
    public static function writeObjectToFile(object:Object, fname:String):void
    {
      var file:File = File.applicationStorageDirectory.resolvePath(fname);

      var fileStream:FileStream = new FileStream();
      fileStream.open(file, FileMode.WRITE);
      fileStream.writeObject(object);
      fileStream.close();
    }
   
    public static function readObjectFromFile(fname:String):Object
    {
      var file:File = File.applicationStorageDirectory.resolvePath(fname);

      if(file.exists) {
        var obj:Object;
        var fileStream:FileStream = new FileStream();
        fileStream.open(file, FileMode.READ);
        obj = fileStream.readObject();
        fileStream.close();
        return obj;
      }
      return null;
    }
  }
}

The read object function basically follows the same flow except it reads the object from the file using the function readObject. It then closes the stream and returns the object. If the file can not be read or the data in the file is incorrect readObject will throw an error. You can handle this with a try catch block if you wanted.

Next, let's take a look at how you would use something like this. Say we are building an application which needs to remember where on the screen it was the last time you opened it. With our object from above it is easy to do something like this. It might even look something like the following.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
 xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="absolute"
 width="300" height="200"
 creationComplete="init()"
 closing="closing()">
  <mx:Script>
   <![CDATA[
     import com.paranoidferret.util.FileSerializer;
     
     private var up:UserPrefs;
     
     private function init():void
     {
       this.up = FileSerializer.readObjectFromFile("prefs.up") as UserPrefs;
       if(up) {
         this.nativeWindow.x = up.appPosX;
         this.nativeWindow.y = up.appPosY;
         this.nativeWindow.width = up.appWidth;
         this.nativeWindow.height = up.appHeight;
         this.nativeWindow.title = up.name;
       } else {
         this.up = new UserPrefs();
       }
     }
     
     private function closing():void
     {
       this.up.name = "The Fattest";
       this.up.appPosX = this.nativeWindow.x;
       this.up.appPosY = this.nativeWindow.y;
       this.up.appWidth = this.nativeWindow.width;
       this.up.appHeight = this.nativeWindow.height;
       FileSerializer.writeObjectToFile(this.up, "prefs.up");
     }
   ]]>
 </mx:Script>
</mx:WindowedApplication>

In the above code we check if there are user preferences available by reading the file on creationComplete in our init function. If we get an object back we cast is as our UserPrefs object and then update the application nativeWindow to be the correct size and position. Otherwise we just create a new user preferences object. To save the information back out we hook into the closing event and write the current size and position back out to our preferences file.

That pretty much wraps it up. I hope this tutorial helps out in some way. At the very least we have learned how easy it is to work with files in AIR. If anyone has any questions as always feel free to leave a comment. I have also included the FileSerializer class in the source files below.

Anonymous
04/04/2009 - 00:47

simple but useful, thanks

reply

Anonymous
10/22/2009 - 05:10

good materilas

reply

Valentyn
10/27/2009 - 03:22

How use this in Flex????????

reply

The Fattest
11/02/2009 - 10:52

In order to use something like this in Flex on the web you would have to have the server handle creating the file for download.

reply

bob
09/17/2010 - 21:39

hi..

I have an ArrayCollection of ValueObjects that I wanted to save out and read back in again later...

package vo
{
        import mx.collections.ArrayCollection;

        [RemoteClass]
        public class afterVO
        {
                public var afterColl:ArrayCollection;
        }
}

the ArrayCollection contains ojects of Value Objects defined as....

package vo
{
        import flash.filesystem.File;

        [RemoteClass]
        public class afterObj
        {
                public var thumb:File;
                public var display:File;
                public var used:Boolean;
        }
}

It appears that the data is written out ok... I look at it with wordpad and I see the info that I expect...

But when I read it back in with.....

beforeV = FileSerializer.readObjectFromFile(app.up.prjName + "/beforeColl.obj") as beforeVO;

it get.....

TypeError: Error #1034: Type Coercion failed: cannot convert Object@bd0a191 to flash.filesystem.File.

When I look at beforeV in debug the 2 file objects thumb and display are null but the boolean var used comes back correct.

So... it has something to do with the file object but I don't know why?

thanks for any insight...

Bob

reply

bob
11/08/2010 - 16:37

So... this worked out great for my air app... quick and easy saving and reading of objects.

Now I want to use these object files for the next part of the project which will be on the web. How (or can) I deserialize these files on my web application? Or will I need to generate XML files for the web app?

Thanks for any insight

Bob

reply

Bart
08/05/2011 - 07:33

Hey just noticed something while trying to implement this in my app: all the var's in object you are serializing (here UserPreferences) HAVE TO BE 'public'

reply

Stefan
10/06/2011 - 18:45

But how to write a circular network of objects? See my question:

http://stackoverflow.com/questions/7681374/serialize-circular-object-networks-using-writeobject-readobject

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.