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. I have also included the FileSerializer class in the source
files below.
Source Files:
simple but useful, thanks
good materilas
How use this in Flex????????
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.
hi..
I have an ArrayCollection of ValueObjects that I wanted to save out and read back in again later...
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
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
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'
But how to write a circular network of objects? See my question:
http://stackoverflow.com/questions/7681374/serialize-circular-object-networks-using-writeobject-readobject