Restoring custom class object array from SharedObject - actionscript-3

I have an array of Widgets (a class I created) called "widgetArray" that I save into a shared object.
savedGame1.data.widgetArray = widgetArray;
When I go to load this data out and use the widgets I run into problems. The array is stored fine, but each widget is saved as an object. So I can't simply do:
widetArray = savedGame1.data.widgetArray;
because then if I try to use the widgets:
widgetArray[0].someWidgetFunction();
it does not work because flash thinks they are objects. I have tried typecasting the widgets when I load them, but it only produces a null object.
widgetArray[i] = widgetArray[i] as Widget;//widgetArray[i] becomes null
tempWidget:Widget = widgetArray[i] as Widget;//null as well
This question is asked in one of the forums from a friend, since there were no responses, i thought this is the right place for it...
How does anyone else deal with this?

Make two methods that can save and load a widget, the save method should take the data from the widget class and save it into the shared object the load class will take the data from the shared object and set the properties of the object.
Something like this :
public function save():Object
{
return {
"x":this.x,
"y":this.y,
"otherStuff":otherStuff
};
}
public function load(data:Object):void
{
this.x = data.x;
this.y = data.y;
this.otherStuff = data.otherStuff;
}
You can call the save method and store the results in an array and then store it in the shared object. You only need to save the data that is required to rebuild the widget class, not all the properties of the class.
Edit : Updated based on BlueRaja's comment.
As BlueRaja pointed out IExternalizable is meant to be used for this.
If you have a class like this :
public class MyClass implements IExternalizable
{
private var one:int = 1;
private var two:int = 2;
public function MyClass()
{
}
public function writeExternal(output:IDataOutput):void
{
output.writeInt(one);
output.writeInt(two);
}
public function readExternal(input:IDataInput):void
{
one = input.readInt();
two = input.readInt();
}
public function print():void
{
trace(one);
trace(two);
}
}
Then you can save it like this:
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var myClass:MyClass = new MyClass();
sharedObject.data['storedObject'] = myClass;
sharedObject.flush();
Then to load it :
registerClassAlias("MyClass", MyClass);
var sharedObject:SharedObject = SharedObject.getLocal("so");
var loadedClass:MyClass = sharedObject.data['storedObject'] as MyClass;
loadedClass.print();
Hope that helps.

http://tush.wordpress.com/2007/07/08/actionscript-3-serializing-classes-using-registerclassalias/ ... This is also a genuine way to store the custom class objects in shared objects

Related

MvvmCross IMvxNavigationFacade, MvxViewModelRequest causes Init() to be called rather than Prepare()

I've implemented an IMvxNavigationFacade for deep linking in my MvvmCross 5.6.x sample app. I've added logic in BuildViewModelRequest() to construct a MvxViewModelRequest with parameters passed in as MvxBundle.
if (url.StartsWith("http://www.rseg.net/rewards/"))
{
var parametersBundle = new MvxBundle();
var id = url.Substring(url.LastIndexOf('/') + 1);
parametersBundle.Data.Add("id", id);
return Task.FromResult(
new MvxViewModelRequest(typeof(RewardDetailViewModel),
parametersBundle, null));
}
However, this approach causes the old style Init() method to be called in the target ViewModel rather than the new typesafe Prepare() method.
public class RewardDetailViewModel :
MvxViewModel<RewardDetailViewModel.Parameteres>
{
...
public new void Init(string id)
{
if (!string.IsNullOrWhiteSpace(id))
{
if (int.TryParse(id, out _rewardId))
RaiseAllPropertiesChanged();
}
}
public override void Prepare(Parameteres parameter)
{
if (parameter != null)
{
_rewardId = parameter.RewardId;
RaiseAllPropertiesChanged();
}
}
}
Is there a way to construct a MvxViewModelRequest so that you pass in an instance of the parameter class for the target ViewModel causing the Prepare() method to be called?
The entire solution can be viewed on GitHub https://github.com/rsegtx/So.MvvmNav2
Thanks in advance!
After doing some research I found at lease one way to accomplish this.
Create a ViewModelInstanceRequest rather than a ViewModelRequest so that you can call ViewModelLoader.LoadViewModel passing in a parameters object; the ViewModelRequest only allows parameters to be passed using a MvxBundle. Make the following change to BuildViewModelRequest() on the NavigationFacade:
var request = new
MvxViewModelInstanceRequest(typeof(RewardDetailViewModel));
var parameters = new RewardDetailViewModel.Parameteres();
.... parse parameters and fill in parameters object
request.ViewModelInstance = ViewModelLoader.LoadViewModel(
request, parameters, null);
return Task.FromResult((MvxViewModelRequest)request);
Create your own IMvxNavigationService and add logic to inspect the object returned from the NavigationFacde and if it is a ViewModelInstanceRequest then use it as is rather than one previously creating.
var facadeRequest = await facade.BuildViewModelRequest(path,
paramDict).ConfigureAwait(false);
...
if (facadeRequest is MvxViewModelInstanceRequest)
request = facadeRequest as MvxViewModelInstanceRequest;
else
{
facadeRequest.ViewModelType = facadeRequest.ViewModelType;
if (facadeRequest.ParameterValues != null)
{
request.ParameterValues = facadeRequest.ParameterValues;
}
request.ViewModelInstance = ViewModelLoader.LoadViewModel(
request, null);
}
I've updated the original example on GitHub https://github.com/rsegtx/So.MvvmNav2.

Nonfunctioning "for loop" for addressing each variable of class

I'm learning AS3 but have some antiquated background in programming (TP and Atari Basic). On this forum I learned to use a loop such as the one below to address each variable in an object class, in order to make a clone of the object (deep or shallow) or in my case to build the text for a tooltip. However mine doesn't work. Here's the loop, following is an explanation, any help you can give I'd appreciate greatly!
var tooltipText:String;
var i:String;
for (i in bsm) {
if (!(bsm[i] is String)) {
if (bsm[i] != 0) {
tooltipText = i + ": " + bsm[i];
tooltip.extendTooltip(tooltipText, 0xFFFFFF);
}
}
}
Please forgive the horrible variable names. 'i' is a String. 'bsm' is a non-null instance of class StatMod, which begins with
public class StatMod extends Object {
public static const ENCHANTMENTMODIFIER:String = "enchantmentModifier";
public var enchantmentType:String = "None";
public var enchantmentDescriptor:String = "None";
public var minDamage:Number = 0;
public var maxDamage:Number = 0;
public var attackSpeed:Number = 0.2;
The intended behavior is to go through each of the variables of StatMod (I'm not showing them all and I will add more later), and if the variable is a non-zero number, make a string ("attackSpeed: 0.2" for instance) and then add that string to the tooltip. The tooltip.extendTooltip function is working properly.
The observed behavior is basically the computer believing that there are no variables in bsm.
What can I say or do to convince the computer that there actually are variabels in bsm?
The behavior you're expecting is only the case when iterating over dynamically attached properties. For example, if you marked your class dynamic:
public dynamic class StatMod { }
Then added some values to it at runtime:
bsm.test = 5;
Your loop will find the property test with the value 5.
Some options you have to achieve what you want are:
Extend the Proxy class to define what properties are iterable via nextName and nextNameIndex.
Use describeType to generate a list of all the public properties.
Though a simpler method is to expose a list of the properties you want to iterate over and use that in your loop instead, something like:
public class StatMod {
// Existing properties etc.
private _properties:Vector.<String>;
public function get properties():Vector.<String> {
if (_properties === null) {
_properties = new <String>[
'enchantmentType',
'enchantmentDescription',
'minDamage',
'maxDamage',
'attackSpeed'
];
}
return _properties;
}
}
Then:
for (var i:int = 0; i < bsm.properties.length; i++) {
var prop:String = bsm.properties[i];
trace(prop, bsm[prop]);
}

Type preservation failing with RemoteClass

Consider the following example dataclass:
[RemoteClass]
public class SOTestData {
public var i:int;
public function SOTestData(i:int) {
this.i = i;
}
}
As I understand, the RemoteClass metadata-tag should ensure that when an object of this class gets sreialized, the type information is preserved.
I used the following program to test:
public class SOTest extends Sprite {
public function SOTest() {
var data:SharedObject = SharedObject.getLocal("SOTest");
if (data.data.object) {
try {
var stored:SOTestData = data.data.object;
trace(stored.i);
} finally {
data.clear();
}
}
else {
data.data.object = new SOTestData(15);
data.flush();
}
}
}
Here the first run writes the data, seconds reads and clears. Running this, I still get a class cast error. Indeed, in the SharedObject there is no type information stored.
I don't think i'm using the metadata wrong, could it maybe be that the compiler doesn't know what to do with it? I don't get any compiler errors/warnings, although when i use some inexistant tag it doesn't complain either. I'm using Flex 4.6 SDK with FlashDevelop as IDE.
EDIT:
Below is the shared object. As you can see, the type is saved as "Object" instead of the actual type.
so = [object #2, class 'SharedObject'] {
data: [object #0, class 'Object'] {
object: [object #1, class 'Object', dynamic 'False', externalizable 'False'] {
i: 15,
},
}
}
I've only used RemoteClass for making AMF RemoteObject calls; I didn't think it had anything to do w/ Shared Objects. Per the docs
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). You insert the
[RemoteClass] metadata tag before an ActionScript class definition.
The [RemoteClass] metadata tag has the following syntax:
As best I can tell from the code you provided, you are not serializing the object in AMF format.
I believe your class cast error is due to the fact that you aren't casting your class. Shared Objects always come back as generic Objects. Try this:
var stored:SOTestData = data.data.object as SOTestData ;
Here is some code from an application I use. First the value object which will get serialized in a shared object:
package com.login.vos
{
[RemoteClass(alias="com.login.vos.UserVO")]
public class UserVO
{
public function UserVO()
{
}
public var firstName :String;
public var lastName :String;
public var userID :Number;
}
}
The the code to save the object:
public static function saveUserVO(userVO:UserVO):void{
var userSharedObject :SharedObject = SharedObject.getLocal('userVO') ;
userSharedObject.data.userVO = userVO;
userSharedObject.flush();
}
And finally, the code to load the objecT:
public static function getUserVO():UserVO{
var userSharedObject :SharedObject = SharedObject.getLocal('userVO')
if(userSharedObject.size <=0){
return null;
}
return userSharedObject.data.userVO as UserVO;
}
The only obvious difference between this and the code by the original poster is that I'm specifying an alias in the RemoteClass metadata.

Shared objects not working correclty in flex -- Mobile

I have a very simple test going with shared objects in flex with mobile I have a person class.
package
{
import flash.display.MovieClip;
public class Person extends MovieClip
{
var personsname:String="";
public function Person(name:String)
{
personsname = name;
}
}
}
And then some simplish code in a view.
var person1:Person;
var person2:Person;
var person3:Person;
var person4:Person;
var thePeople:Array=[];
var so:SharedObject;
function init():void{
person1 = new Person("james");
person2 = new Person("mike");
person3 = new Person("Amanda");
person4 = new Person("Shelly");
thePeople.push(person1,person2,person3,person4);
//so = SharedObject.getLocal("savedData"); //clear it again
///so.clear(); // clear it again
savePeople();
getPeople();
}
private function savePeople():void{
so = SharedObject.getLocal("savedData");
if(so.data.thePeopleArray == null){
so.data.thePeopleArray = thePeople;
so.flush();
}
}
private function getPeople():void{
so = SharedObject.getLocal("savedData");
var thePeeps:Array = so.data.thePeopleArray;
trace(thePeeps);
}
The first time I run this it traces out
[object Person] 4 times
I close the emulator and rebuild and run it traces out
,,,
If I clear out the so it show the [object Person] again, but comment out get the ,,,
Can shared objects even store an array of objects properly. It is the same with the persistanceManager I believe.
The root of the problem here is that you are trying to save an instance MovieClip into the SharedObject. Since the MovieClip is an intrinsic object (native to flash) it cannot be converted into a form which can be stored. This causes flash to convert the data into a generic Object which is stored to disk. I can only guess at exactly what is going into the SharedObject at this point.
It seems to work the first time because flash does not actually load the shared object in the getPeople() call, it just uses the object which is already in memory. The second time the app runs it reads the generic object from disk and creates a generic object.
There is another problem which is that the flash player does not know to pass data to the constructor when it reads the object.
There are a few possible workarounds, some are:
Store the data as text
Store the data as a ByteArray
Store the data in a "Data Object"
Each of these requires some conversion during the read and write process, but this can be simplified using an interface. This also adds flexibility in case your object changes you will still be able to read the data in the SharedObject.
1: Text
As an example, you might add two methods to the Person object, call them serialise() and deserialise(). The serialise() method will return text which can be stored in the shared object. The deserialise() will parse text and populate the values of the object.
Here's a sample to illustrate this:
class Person {
private var name:String;
private var age:int;
public function serialise():String {
return [name, age].join("\t");
}
public function deserialise(input:String):void {
var tokens:Array = input.split("\t");
name = tokens[0];
age = parseInt(tokens[1]);
}
public static function create(name:String, age:int):Person
{
var output:Person = new Person();
output.name = name;
output.age = age;
return output;
}
}
For ease of use we can create a class for managing a collection of people:
class People {
private var people:Vector.<Person> = new Vector.<Person>();
public function clear():void {
people.splice(0, people.length);
}
public function add(person:Person):void {
people.push(person);
}
public function serialise():String {
var output:Array = [];
for each (var person:Person in people)
output.push(person.serialise());
return output.join("\n");
}
public function deserialise(input:String):void {
var tokens:Array = input.split("\n");
for each (var token:String in tokens) {
var person:Person = new Person();
person.deserialise(token);
add(person);
}
}
public function save():void {
var so:SharedObject = SharedObject.getLocal("cookie");
so.data.people = serialise();
so.flush();
}
public function load():void
{
var so:SharedObject = SharedObject.getLocal("cookie");
if (so.data.people != null)
deserialise(so.data.people);
}
}
Usage:
var people:People = new People();
people.load();
trace(people.serialise());
people.clear();
people.add(Person.create("Candy", 21));
people.add(Person.create("Sandy", 23));
people.add(Person.create("Randy", 27));
people.save();
trace(people.serialise());
An obvious flaw in this example is that the \n and \t characters cannot be used as part of the data (ie for the name of a person). This is a common short-coming with text data.
** Update: Look into the built-in JSON methods for a consistent approach to serialising objects to and from text.
2: ByteArray
Very similar to the text method described above, except the serialise/deserialise methods would accept an additional parameter of a ByteArray, which the object would write to. The ByteArray would then be saved and loaded from the shared object. The advantages of this method is that resulting data is usually is compact and versatile than the text method.
Flash also defines the IDataInput and IDataOutput interface which can be used here.
3: Data Objects
If you still prefer the storing objects directly, then you could create a proxy object which serves the sole purpose of carrying data. A data object (aka DO) is a an object which only has variables, and not methods. Eg:
class PersonDO {
public var name:String;
}
It would be used something like this:
var person2:Person;
var person3:Person;
var person4:Person;
var thePeople:Array=[];
var so:SharedObject;
function init():void{
person1 = new Person("james");
person2 = new Person("mike");
// store the people data into data objects
person1DO = new PersonDO();
person1DO.name = person1.name;
person2DO = new PersonDO();
person2DO.name = person2.name;
thePeople.push(person1DO,person2DO);
savePeople();
// load the people into data objects
getPeople();
person1 = new Person(thePeople[0].name);
person2 = new Person(thePeople[1].name);
private function savePeople():void{
so = SharedObject.getLocal("savedData");
if(so.data.thePeopleArray == null){
so.data.thePeopleArray = thePeople;
so.flush();
}
}
private function getPeople():void{
so = SharedObject.getLocal("savedData");
var thePeeps:Array = so.data.thePeopleArray;
trace(thePeeps);
}
Even though this may appear simpler than the alternatives there are downsides to storing objects directly:
- Stored data is very fragile - if you change the object then your data will become unusable unless you have several versions of each object.
- You need to ensure that a reference to the data object is compiled into the application.
- A common usage scenario for Shared Objects is to save data objects from one SWF, and load them in another. You need ensure that both SWFs use identical version of the class being saved and loaded.
Hope that helps.

Trying to understand the AsyncToken in Flex/Actionscript

I am trying to understand the way the AsyncToken works in actionscript. How can I call a remote service and ensure that a specific parameter is available in the result or fault event functions? I think it is the async functionality I want to use.
The following code will hopefully explain what I am trying to do. Feel free to modify the code block as your explanation.
Thanks.
public function testSerivceCall(data:Object, callBackCommand:String):void
{
// Assume callBackCommand == "FOO";
// How can I pass in callBackCommand as a parameter to the result or fault events?
// How do I create an async token here?
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
remoteObject.test(data);
}
private function _handleTestResult( event:ResultEvent ) : void
{
// How do I get the async token value?
// How can I get the value of callBackCommand in this code block?
if (callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
// How do I get the async token value?
// How can I get the value of callBackCommand in this code block?
}
An edit to make this question more clear:
Assume I make the following method call somewhere in my code:
testSerivceCall(personObject, "LoginCommand");
How do I get access to the actual string "LoginCommand" inside the _handleTestResult function block?
The reason I want to do this is because I want to dynamically call back certain functions and hand off the result data to specific commands that I know ahead of time when I am making the service call.
I am just having a time grokking the AsyncToken syntax and functionality.
I did not even need closures. I added a class as below which I called externally.
The call was like this:
public class MyClass
{
...
var adminServerRO:AdminServerRO = new AdminServerRO();
adminServerRO.testSerivceCall("FOO",cptyId);
}
public class AdminServerRO
{
private function extResult( event:ResultEvent, token:Object ) : void
{
//the token is now accessed from the paremeter
var tmp:String = "in here";
}
private function extFault( event:FaultEvent ) : void
{
var tmp:String = "in here";
}
public function testSerivceCall(callBackCommand:String, cptyId:String):void
{
var remoteObject:RemoteObject = new RemoteObject();
remoteObject.destination = "adminServer";
var token:AsyncToken = remoteObject.getCounterpartyLimitMonitorItemNode(cptyId);
token.addResponder(new AsyncResponder(extResult,extFault,cptyId));
}
}
While the accepted answer will accomplish what the original submitter wants it does not actually answer the question which was asked. An AsyncToken is created as a result of a remote method call and is accessible from the ResultEvent. Since AsyncToken is a dynamic class you can add whatever property to it that you want. The code below should demonstrate this:
public function testSerivceCall(data:Object, callBackCommand:String):void
{
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
var token:AsyncToken = remoteObject.test(data);
token.callBackCommand = callBackCommand;
}
private function _handleTestResult( event:ResultEvent ) : void
{
if (event.token.callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
//event.token.callBackCommand should be populated here too
}
If you want to access the properties used during the remote call (parameters to the call and/or AsycToken), you can make use of closures. Just define the result event handler inside the calling method as a closure. It can then access any variable in the calling function.
public function testSerivceCall(data:Object, callBackCommand:String):void
{
var _handleTestResult:Function = function( event:ResultEvent ) : void
{
// token is visible here now
if (callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
var token = remoteObject.test(data);
}
If I'm reading your question correctly, you're trying to figure out how to access the actual data returned by the ResultEvent ?
If so, assuming you've made the call correctly and you've gotten data back in a format you're expecting:
private function _handleTestResult( event:ResultEvent ) : void
{
// you get the result from the result property on the event object
// edit: assuming the class Person exists with a property called name
// which has the value "John"
var person : Person = event.result as Person;
if (person.name == "John")
{
Alert.show("John: " + person.name);
}
else
{
Alert.show("Not John: " + person.name);
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
// Maybe you know the type of the returned fault
var expectedFault : Object = event.fault as MyPredefinedType
if (expectedFault.myPredefinedTypesPredefinedMethod() == "BAR")
{
// something here
}
}
The ResultEvent has a property called result which will hold an instance of the object returned by the result (it might be the output of an XML file if using a web service, or a serialized object if using AMF, for example). This is what you want to access. Similarly, FaultEvent has a fault property that returns the fault information.
Edit: Changed code in _handleTestResult() in response to Gordon Potter's comment.