Flash is a great platform. You can build applications for the browser, the desktop, and… well, what else is there?
When building applications, especially those with a document-based model such as the Aviary apps, Odosketch, My Canvas, ZenStudio, the apps on acrobat.com, and many others, you need a file format for the document or project. Or some way to save it.
You don’t want to save each item into a table in the database. I know a guy…who had a dream…that his friend did this. This guy’s friend in his dream had a table for each item that needed to be stored with foreign keys etc. When a project needed to be loaded, the server-side script did a ton of queries and created a sometimes-quite-large XML structure that it then sent to the Flash app. When saving, this XML was sent back and parsed back into the database with INSERTS and UPDATES.
That does not scale, and none of that data was needed to be pulled up in reports or anything that you might consider useful and require a database for. It was complex, hard to add features, and slow. That is, it would have been had that guy’s dream friend been real.
Saving document/project files to disk is the way to go. You can store metadata about each project or document in a database if you are storing them server-side. If it is an AIR app, give it a unique extension and the user can double-click on the file to open it. If you are AIR only, you can save your file as a SQLite database file. Pretty sweet option, but doesn’t work for the browser ’cause Flash can’t do that. The format could be XML like OpenOffice documents and the new MS Office formats. You would need to parse the files in and out and they could get very large, so you’d want to compress them anyway.
So, XML might be the most portable format, but hey, that’s what Import/Export dialogs are for, right? We’re talking about Save/Save As… :) Enter our new contender, AMF! AMF isn’t really new, however I haven’t heard of many people using it as their file format. The cool thing about using AMF is that after you’ve prepared your objects for it, you could store (in player 10) pieces of your document on the clipboard for copy/paste from one place to another (even after a page refresh or from AIR to browser!). You can do an auto-save of a document or page to the user’s SharedObject store.
To use AMF as the format for you data you’ll need to prepare your objects sufficiently. Here are a few rules:
The way to store your document object or project object (or page, or widget, or whatever) to AMF depends on where you are storing it. There are a lot of Flash APIs that use AMF already. If you are storing it to SharedObject, just assign the item to the data property or one of its properties.
SharedObject.getLocal("_myProject").data.project1 = myProject;
To send it from one app to another over LocalConection is just as easy.
var conn:LocalConnection = new LocalConnection();
conn.send("_connName", "passProject", myProject);
To send it to the server vi Flash Remoting you pass the object as-is the same as with LocalConnection.
If you want to save the project to disk or pass it to the server in a RESTful manner or as a file upload, you’ll need to serialize it to AMF first. But that is pretty easy. Just remember to compress after you write it and uncompress before you read. This will save a lot of room.
var byteArray:ByteArray = new ByteArray();
byteArray.writeObject(myProject);
byteArray.compress();
// write the bytearray to file or send to server
// or we can pull it back out making a clone! (save as...)
byteArray.position = 0;
myProject = byteArray.readObject();
Easy cheesy. I will leave it as an exercise to the reader to figure out how to store it to the clipboard for copy/paste.
XML might be an easier format, though more verbose and prone to errors in the creation and parsing. Though it could allow for greater flexibility and accessibility to other programs to read the file.
Let me know if you use AMF for your document/project file storage. I’d be interested to hear how many people use this method and how well it has worked out for you.
I considered using AMF as the format for levels in one of my Flash games, but I decided that if I wanted to port to another platform in the future, it could be difficult to reuse those files. Still, it’s a tempting prospect.
Thanks a lot, Jacob, for pointing out this possibility! I’ve been messing with as3 zip implementations, FAR FlashArchive etc. just to be able to loading bunches of aggregated files – this way it should be possible to replace these with my own custom AMF formats.
Used in combination with Robert Taylors HttpAmf solution, this should be really slick… :-)
[...] a recent article, Jacob is pointing out the possibility to use AMF serialization for storing objects to disc, on [...]
[...] the idea and most of the solutions from Robert Taylor’s and Jac Wright’s excellent blogs. [...]
Hey great article, Jacob! Really helpful and thought provoking.
Where you say “This allows you to get around the constructor parameter issue, but it is more work”, this implies to me that implementing IExternalizable allows those classes to be instantiated even if they have mandatory constructor parameters. This doesn’t seem to be the case. Could you elaborate, please?
I was a bit disappointed when I realised that the constructor is called when reserializing objects. I guess it’s probably unavoidable, but I would have preferred some magic: the deserializer could have “silently” instantiated the object without calling the constructor, setting all the properties (or calling readExternal), then calling some kind of “wakeupExternal” method, which could have been part of the IExternalInterface.
Maybe that’s a bit crazy or naïve, but it would make adding serialization to my current project a lot easier, as it makes serialization pretty much completely transparent.
Ah, sorry for the ambiguity there. How I used this was I created a BitmapData wrapper, like ArrayCollection wraps an array (but more light-weight).
So MyBitmap implements IExternalizable and has a “source” property of type BitmapData. When MyBitmap gets written to the stream I write the width, height, and then BitmapData.getPixels to the stream. When it gets read back out I read the width and height out, create a new BitmapData object with those parameters, then copyPixels.
So, I am able to create the inner object myself and use constructor parameters. Hope this might help.
Hi Jacob,
Good article. I use the same technique myself. I’m wondering if you stumbled upon any information on the byte array message format itself; the docs say it’s AMF format but when I look at the byte code it’s not exact.
The reason I ask is that I would like another language to generate my files, for example, C++ or PHP into that format. I’ve tried using the Zend framework but it doesn’t work. If you want to automate anything with flash, good luck.
Mark
Hey Mark,
There is several parts to AMF. There’s the object encoding, and then there’s the message format with its headers and such. If you’re just saving data to a file, you don’t need the message portion of the AMF spec that remoting clients need. So the byte code should be exact for the encoded object part, but missing the message parts.
If you want to generate your files or read them from another library, you’ll need to be sure to just use their serialization and deserialization classes and not the rest of the message reading. I am doing this in my RestServer in PHP using Zend. If you check out
http://jacwright.com/blog/250/simple-rest-server-in-php-supports-json-amf/
you can read about it and see how I do it in
http://jacwright.com/blog/resources/RestServer.txt
if ($this->format == RestFormat::AMF) {
require_once ‘Zend/Amf/Parse/InputStream.php’;
require_once ‘Zend/Amf/Parse/Amf3/Deserializer.php’;
$stream = new Zend_Amf_Parse_InputStream(substr($data, 1));
$deserializer = new Zend_Amf_Parse_Amf3_Deserializer($stream);
$data = $deserializer->readTypeMarker();
}
Hope that helps.