actionscript-3apache-flexmobileflash-builderpersistence-manager

flash builder mobile, using PersistenceManager properly?


I'm new to mobile app dev, but I'm trying to build a mobile RPG game character sheet app. Instead of using SQLite, Im trying to use PersistenceManager to persist the character data when the app is interrupted or shutdown, but Im not sure if Im using it properly. Heres what I have so far.

Main.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                        xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.CharactersView" 
                        applicationDPI="160" persistNavigatorState="true">

</s:ViewNavigatorApplication>

CharactersView.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="creationCompleteHandler(event)">

<fx:Script>
    <![CDATA[
        import mx.collections.ArrayCollection;
        import mx.events.FlexEvent;

        protected function creationCompleteHandler(event:FlexEvent):void
        {
            var o:Object = new Object();
            o.name = "Aragorn";
            o.type = "Ranger";
            o.strength = 97;
            o.speed = 7;
            Database.insertCharacter(o);
            trace(Database.characters.length);
        }

    ]]>
</fx:Script>

<s:List id="list" width="100%" height="100%"/>

Database.as:

import spark.managers.PersistenceManager;

public class Database
{
    private static var data:Array = [];
    private static var pm:PersistenceManager = new PersistenceManager();

    public static function get characters():Array
    {
        if (!pm.load() || pm.getProperty("characters") == null)
        {
            Database.data = [];
        }
        else
        {
            Database.data = pm.getProperty("characters") as Array;
        }
        return data;
    }

    public static function insertCharacter(o:Object):void
    {
        Database.data.push(o);
        pm.setProperty("characters", Database.data);
        pm.save();
    }

}

In the creationComplete handler of CharactersView.mxml, I'm creating a one-off character with some arbitrary info just to test if inserting characters works. Every time I run this, completely as-is, the trace statement (length of my array) returns 1. Shouldn't it go up by one every time I run it? This is telling me something isn't right, but is it the persistencemanager not saving the data properly? Do I need to check if pm.save() returns true, and if it does, should I dispatch an event and only THEN run the trace statement to get the proper length? That seems overly complicated just to save some data, so I'm not entirely sure whats going on or how to fix it.

In any case, what I'm asking is if my current setup is correct, and if its not, does anyone have a simple example, or could create a simple example based on my code for how to properly use persistencemanager?

Once I get this technique down I can create several other apps as well, so Im really hoping to get this nailed down. Thanks in advance!

EDIT: It seems I'm using everything correctly regarding the PM and the answer I received did help me get my app working. However, when moving forward my app started acting funny. For instance, my list seems to react really slowly to interaction, as do most of the other components. New code below.

Main.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                        xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.CharactersView" 
                        applicationDPI="160" persistNavigatorState="true">

</s:ViewNavigatorApplication>

CharactersView.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="creationCompleteHandler(event)">

<fx:Script>
    <![CDATA[
        import mx.collections.ArrayCollection;
        import mx.events.FlexEvent;

        protected function creationCompleteHandler(event:FlexEvent):void
        {
            update();
        }

        private function update():void
        {
            title = "D&D Characters (" + Database.characters.length + ")";
            list.dataProvider = new ArrayCollection(Database.characters);
        }

        protected function button1_clickHandler(event:MouseEvent):void
        {
            var vo:CharacterVO = new CharacterVO();
            vo.name = "Aragorn";
            vo.className = "Ranger";
            vo.race = "Human";
            vo.level = 6;
            vo.gender = "Male";
            vo.alignment = "Unaligned";
            vo.hp = 25;
            vo.xp = 100;
            Database.insertCharacter(vo);
            update();
        }

    ]]>
</fx:Script>

<s:actionContent>
    <s:Button label="+" click="button1_clickHandler(event)"/>
</s:actionContent>

<s:List id="list" width="100%" height="100%"/>

</s:View>

Database.as:

import spark.managers.PersistenceManager;

public class Database
{
    private static var data:Array = [];
    private static var pm:PersistenceManager = new PersistenceManager();

    public static function get characters():Array
    {
        return data;
    }

    public static function insertCharacter(o:CharacterVO):void
    {
        Database.data.push(o);
        pm.setProperty("characters", Database.data);
        pm.save();

        if (!pm.load() || pm.getProperty("characters") == null)
        {
            Database.data = [];
        }
        else
        {
            Database.data = pm.getProperty("characters") as Array;
        }
    }

}

CharacterVO.as:

import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.utils.IExternalizable;

public class CharacterVO implements IExternalizable
{
    public var name:String;
    public var className:String;
    public var race:String;
    public var gender:String;
    public var alignment:String;
    public var level:Number;
    public var hp:Number;
    public var xp:Number;


    public function writeExternal(output:IDataOutput):void {
        output.writeUTF(name);
        output.writeUTF(className);
        output.writeUTF(race);
        output.writeUTF(gender);
        output.writeUTF(alignment);
        output.writeFloat(level);
        output.writeFloat(hp);
        output.writeFloat(xp);
    }

    public function readExternal(input:IDataInput):void {
        name = input.readUTF();
        className = input.readUTF();
        race = input.readUTF();
        gender = input.readUTF();
        alignment = input.readUTF();
        level = input.readFloat();
        hp = input.readFloat();
        xp = input.readFloat();
    }
}

As you can see most of the code is the same as what I first posted. I added the CharacterVO class, I moved a few things around in CharactersView.mxml, and I moved the PersistenceManager loading code to the insertCharacter function (this way, I'm only loading the PM whenever a new item is added, not everytime I call Database.characters).

Again, my app seems to react real slow to interaction, sometimes it doesnt react at all. Something just isnt right, Ive never had this issue with a mobile app before. Could it be that storing the array in the PM and then attempting to set it as the List's dataProvider is causing an issue?


Solution

  • I'm not too familiar with using this however from the docs it looks like you probably can't use a generic Object since I don't believe it implements IExternalizable, according to the docs this doesn't cause an error for some reason but it can't actually read/write those objects.

    http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/spark/managers/PersistenceManager.html

    When storing values using the manager, it is important that all values can be properly be written to a shared object. Complex objects that store classes or non-standard flash primitives must implement flash.net.IExternalizable interface to work properly. Saving incompatible objects does not cause an RTE, but creates undefined behavior when the data is read back.

    When using this myself, one time helping another SO poster on a project I don't recall really having any issues except if we changed properties of the model object we were storing/retrieving then we had to manually blow away the local shared object it was creating otherwise it couldn't de-serialize it (makes sense but just something to be aware of). I don't actually recall implementing IExternalizable but it's possible the other guy did it.

    Try this:

    [CharacterVO.as]

    public class CharacterVO implements IExternalizable
    {
        public var name:String;
        public var type:String;
        public var strength:Number;
        public var speed:Number;
    
    
        public function writeExternal(output:IDataOutput) {
            output.writeUTF(name);
            output.writeUTF(type);
            output.writeFloat(strength);
            output.writeFloat(speed);
        }
    
        public function readExternal(input:IDataInput) {
            name = input.readUTF();
            type = input.readUTF();
            strength = input.readFloat();
            speed = input.readFloat();
        }
    }
    

    Then instead of creating a Generic object create instances of that VO. I believe Array will serialize automatically by serializing all it's elements in order.