AS3/Flex - persistNavigatorState + objects with ArrayCollection() - actionscript-3

I currently have a mobile application on the playbook that has the following class:
[Bindable]
public class Foo
{
public var myString:String;
public var myList:ArrayCollection;
public function Foo() {}
}
I also have persistNavigatorState="true" in my ViewNavigatorApplication.
Suppose in my first view I have the following in my creationComplete="init()" call:
private function init():void {
var s:String = "test_string";
var a:ArrayCollection = new ArrayCollection();
a.addItem("test1");
a.addItem("test2");
a.addItem("test3");
data.foo = new Foo();
data.foo.myString = s;
data.foo.myList = a;
trace(data.foo.myString);
trace(data.foo.myList[0]);
trace(data.foo.myList[1]);
trace(data.foo.myList[2]);
}
When executed, everything works fine in my app. However, since I want the sessions to persist in case the user accidentally closes the app, when he re-opens it the data should still be there.
Instead, when I close and re-open my app only the myString property persists (ie traces "test_string", as intended), however the ArrayCollection isn't copied.
I've tried the following with ObjectUtil.clone() and ObjectUtil.copy():
data.foo.myString = ObjectUtil.copy(s) as String;
data.foo.myList = ObjectUtil.copy(a) as ArrayCollection;
and I've also tried:
var f:Foo = new Foo();
f.myString = s;
f.myList = a;
data.foo = ObjectUtil.copy(f) as Foo;
trace(data.foo.myString);
trace(data.foo.myList[0]);
but this only throws me a
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Any ideas on how to persist ArrayCollections and Foo class in a mobile application?

I'm not 100% sure, but I was wondering about this type of problem while working on a mobile app recently.
I believe your problem might be happening b/c your are setting the data manually on the View, instead of passing it into the View with the ViewNavigator.pushView() method.
I just browsed through the source, and it looks like setting the data directly on the View will bypass ViewNavigator's data persistence. Though with that said, I'm not sure why it would even remember the value for that String :)
I would try to do the following:
don't set the View's data property from inside the view, as you are doing now in the creationComplete handler
if possible, use the "firstView" property of ViewNavigatorApplication in mxml
if possible, initialize the "firstViewData" property in mxml (may not be possible)
if you can't do the two above, in your application's startup code call navigator.pushView(View_Class_Name, foo) to pass in the data.

Related

SharedObject Not saving data safely?

I try to have an object with some immutable properties which initiate at the constructor and won't be able to change after initiation. Those properties then only have getter method. Since I want this object can be shared with other classes, I save it to the SharedObject class called saveIt().
All work fine when running the app on iOS (I do not test it on Android yet) except that if I completely shutdown the app from memory and reopen it, those properties' without setter are lost. The code is as below:
var rooms:ArrayCollection = saveData.read("rooms") as ArrayCollection;
if (!roomExists) {
var room:Room;
room = new Room(myID, myName, maxRoomSize);
room.name = rm.name;
room.timestamp = new Date();
room.joinUrl = rm.joinUrl;
room.attendeeUrl = rm.attendeeUrl;
room.attendees = conferenceParameters.metadata.hasOwnProperty("attendees") ? conferenceParameters.metadata["attendees"] : null;
rooms.addItem(room);
}
saveIt.save("rooms", rooms);
I found if I assign a dummy setter for each of the immutable properties i.e.
function set myID(s:String):void{}
then it can be safely saved even I shutdown the app. Since I can't find any information about this code behavior, so I'd like to look for opinions here!!

ActionScript: making a variable `[Bindable]` causes crashes

I have this singleton that I'm using as a wrapper for global variables and constants, but as soon as I make some [Bindable] I get a crash on start up w/a bunch of red text in my console.
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at BrandGlobals$/get COLOUR_EVERYTHING_BACKGROUND()[C:\MyProject\src\BrandGlobals.as:14]
at BrandGlobals$cinit()
at global$init()[C:\MyProject\src\BrandGlobals.as:2]
at _mainWatcherSetupUtil/setup()
at main/initialize()[C:\MyProject\src\main.mxml:0]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::childAdded()[C:\autobuild\3.5.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:2131]
at mx.managers::SystemManager/initializeTopLevelWindow()[C:\autobuild\3.5.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3400]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::docFrameHandler()[C:\autobuild\3.5.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3223]
at mx.managers::SystemManager/docFrameListener()[C:\autobuild\3.5.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3069]
BrandGlobals:
package {
public final class BrandGlobals {
[Bindable]public static var COLOUR_EVERYTHING_BACKGROUND:uint = 0xE010FF;
If I remove that [Bindable] and turn var to const there's no problem (except the obvious problem of not being able to set the variable outside of this file) but this doesn't work. Also, making the whole class [Bindable] instead of this one didn't work. When I hover my mouse over the COLOUR_EVERYTHING_BACKGROUND definition, it says "<exception thrown by getter>". 'Don't know what to think about that.
I might have guessed it was because it has no package, but I'm using another similar singleton which has [Bindable] variables and seems to work fine.
I never did get that [Bindable] twaddle.
I'm using the Flex 3.5 SDK.
I tried Brian's suggestion below, but it gave me pretty much the same error. I even tried:
{
_COLOUR_EVERYTHING_BACKGROUND = 0xE010FF;
trace("Var set."); //Breakpoint here
bLoadedFerCryinOutLoud = true;
}
[Bindable]private static var _COLOUR_EVERYTHING_BACKGROUND:uint;
private static var bLoadedFerCryinOutLoud:Boolean = false;
public static function get COLOUR_EVERYTHING_BACKGROUND():uint {
trace("Returning EVERYTHING background");
if (bLoadedFerCryinOutLoud)
return _COLOUR_EVERYTHING_BACKGROUND;
else return 0xFFFFFF;
}
What's more, if I put a breakpoint at that trace("Var set.");, Flash Builder complains that a break is not possible, because there is no executable code there.
I also noticed that in that call stack that I'm shown when this crash happens during a set and it seems to be the one that sets _COLOUR_EVERYTHING_BACKGROUND. But the only place where it is set is:
public static function SetBackground(oApp:UBIApplication):void {
_COLOUR_EVERYTHING_BACKGROUND = oApp.nBackgroundColour;
}
and breakpoints indicate that this is never called.
The documentation on using the tag has the following to say:
Using static properties as the source for data binding
You can use a static variable as the source for a data-binding expression. Flex performs the data binding once when the application starts, and again when the property changes.
You can automatically use a static constant as the source for a data-binding expression. Flex performs the data binding once when the application starts. Because the data binding occurs only once at application start up, you omit the [Bindable] metadata tag for the static constant. The following example uses a static constant as the source for a data-binding expression:
<fx:Script>
<![CDATA[
// This syntax casues a compiler error.
// [Bindable]
// public static var varString:String="A static var.";
public static const constString:String="A static const.";
]]>
</fx:Script>
<!-- This binding occurs once at application startup. -->
<s:Button label="{constString}"/>
Edit: You need to make sure that your variable is initialized before you try to read it. A static initializer is the way to go:
package {
public final class BrandGlobals {
{
_COLOUR_EVERYTHING_BACKGROUND = 0xE010FF;
trace("Var set."); //Breakpoint here
}
[Bindable]private static var _COLOUR_EVERYTHING_BACKGROUND:uint;
public static function get COLOUR_EVERYTHING_BACKGROUND():uint {
trace("Returning EVERYTHING background"); //Breakpoint here
return _COLOUR_EVERYTHING_BACKGROUND;
}
Putting in breakpoints in the places specified will let you verify that things are executing in the expected order
It turns out that the problem was assigning COLOUR_EVERYTHING_BACKGROUND to a static const elsewhere in the code, as a temporary measure. Hopefully I'll remember that assigning [Bindable]s to static consts is bad and if I don't, I'll remember the meaning of that particular cryptic reaction Flash Builder had. I'm starting to choke StackOverflow w/my questions about cryptic error messages.

Why does a closed NetConnection that has no event listeners or references stick around in memory?

It seems that if flash.net.NetConnection is instantiated and connected to an HTTP URL (such as an AMFPHP gateway), that instance is never picked up by garbage collection even after it has been closed and the only reference is set to null.
On the other hand, if the instance is connected to null (as would be done when used to play video/mp3 files), the instance is cleared from memory.
To clarify, the following connection will stick around in memory:
var stickyConn:NetConnection = new NetConnection();
stickyConn.connect("http://myserver/amfphp/gateway.php");
stickyConn.close();
stickyConn = null;
Whereas, the following connection will be cleared from memory immediately:
var tempConn:NetConnection = new NetConnection();
tempConn.connect(null);
tempConn.close();
tempConn = null;
Some things I have already tried to solve this issue:
set the client to an empty object (since the default value of the client is the NetConnection itself)
before closing the connection, call connect(null)
after closing the connection, call connect(null) and close it again
Has anyone run into this issue before? Is there a solution to this?
I have built heavyloaded FLV/Mp4 Players using AS3 quite often. When I am using a service like Akamai or Adobe's internal NetConnection Class I always keep in mind the
client object.
the is the property of NetConnection on which ALL callback methods are invoked. The default is this NetConnection instance this. If you set the client property to another object, callback methods will be invoked on that object.
In this way you can easily understand how Garbage Collection was never really applied accross each component in the same way. So, where stickyConn = null; only stops the playback, since you never declared a Weak Reference, Garbage Collection has no clue what to look for.
I have had success with differrent methods based on the specific player:
Simply stating NetConnectionObj.client = this usually suffices. But what if your NetConnection is extended or implementing an interface? Simply use a null Dictionary object:
var d:Dictionary = new Dictionary(true); . From here Garbage collection will recognize "d" as a weak reference and automatically dump it;
Hence, your snippet will look somewhat like this:
var Dc:Dictionary = new Dictionary(true);
NetConnection:NetConnection.client = Dc;
or some variation with the same intent.
I know this works, so reach out if you need help...
I may have been vague with the last answer in regards to GC and Dictionary Objects. Please take this snippet into consideration. I wrote it quickly but I try to explain the Concept of what solves your problem; mainly since I have dealt with it before:
public class Main extends MovieClip {
private var connection:NetConnection;
private var __nData:*;
private var _instance:*;
private var _closure:Function;
private var _D:Dictionary;
public function Main() {
connection = new NetConnection();
connection.addEventListener(NetStatusEvent.NET_STATUS, _nsHandle)
connection.connect(null);
}
public function _nsHandle(event:NetStatusEvent):void {
try {
connection = new NetConnection();
connection.connect(null);
connection.client = RegisterForGC(event.target);
RegisterForGC(connection);
} finally {
__nData = event.target.netConnection;
}
}
public function RegisterForGC(NCObject:*):* {
_instance = NCObject;
_closure = function ():void {}
_listener = function (e:Event):void {}
_D = new Dictionary(true);
_D[_listener] = "A";
_D[_instance] = "B";
_D[_closure] = "C";
try {
new LocalConnection().connect( "A" );
new LocalConnection().connect( "B" );
} catch (anything:*) { }
return _instance;
}
}
I'm not sure but Your example seems to suggest you are declaring your vars on stage / frame.
close(); is all you need for this to work HOWEVER....
from what I have found with NetConnection it for some reason unless all vars / functions are declared in an External class eg. public vars public function,
It stays in memory even after using close();
Pulled out my hair figuring this out with an audio streaming project.
However once I moved all coding to an external class, close(); actually closed the connection.
If your code is on a frame on stage or within MC I would create a class and declare vars & functions in said External Class.as and for some stupid reason it works.
Hope this helps.
Are you using a NetStream object and not disposing of it when finished? I only ask because I rarely see a NetConnection without a NetStream object far behind it.

Value Will Set properly, but Get receives Null

So, I have successfully grabbed a value out of an XML document and set it into a separate class called "AddCommas." The trace functions have shown me that it sets properly.
For more details, my objective is to take the language indicator ("fr" for french or "en" for english), set it inside the appropriate class and into a variable I will use. Now, I am using this variable to be used in an if statement; which will help me format a number properly (commas, decimals, spaces) per the clients request.
However, my problem is when I try to get the value to use it. It always comes back as Null. I have placed traces all over my program trying to pinpoint when this happens, but I cannot find it. Here's the code...
The pull from the XML file and into the set (this works fine, but I am adding it for your benefit in case I missed something)
public var commaHold = new AddCommas();
localLanguage = xmlObj.localLanguage;
trace("localLanguage + " + localLanguage);
commaHold.setLanguage(localLanguage); // Set Language
//More code follows...
This is the set function istelf...
public function setLanguage(localLanguage:String){
langHold = localLanguage;
trace("Set Language = " + langHold); //This always shows a successful set
}
Now am I wrong in thinking that in AS3, once langHold in my AddCommas class has been set I should be able to use it without calling a get within the function I am using the If Statement in, right? Such as this?
var language = langHold;
if (language == "en"){
trace("Language is = " + language); // More code follows afterwards and as of now, this shows NULL
Now, I have attempted plenty of Get functions to add the language variable in the call itself to this function and it's always the same. Am I missing some fundamentals here?
Thank you very much for your time.
If you expect a string comparison you need to use quotes, unless en is a String variable since langHold is a String, like:
if (language == "en"){
Consider modifying the set function to use the as3 keyword like:
private var _language:String;
public function set language(value:String):void {
_language = value;
//do other stuff here if necessary, put a breakpoint on the line above
}
public function get language():String{
return _language;
//put a breakpoint on the line above
}
You should be able to see when any instance of your class has the property changed. The only other issue I can think of is it is not the same instance of the class and therefore doesn't share the property value you set earlier. In the debugger you can check the "hashCode" or "address" it shows for this to see if it changes when it hits the breakpoints.
Here's a sample Singleton structure in AS3 (this all goes in one file):
package com.shaunhusain.singletonExample
{
public class SingletonExample
{
private static var instance:SingletonExample;
public static function getIntance():SingletonExample
{
if( instance == null ) instance = new SingletonExample( new SingletonEnforcer() );
return instance;
}
/**
*
* #param se Blocks creation of new managers instead use static method getInstance
*/
public function SingletonExample(se:SingletonEnforcer)
{
}
}
}
internal class SingletonEnforcer {public function SingletonEnforcer(){}}
using this single shared instance from any other class would look something like this:
private var singletonInstance:SingletonExample = SingletonExample.getInstance();
ShaunHusain's theory of using a Singleton was the perfect solution I needed. However, his code gave me a bizarre 1061 error and my format and code appeared to be error free. Regardless, I looked up another way to use a Singleton as follows that worked perfectly for me. Honestly, Shaun's code should work for anyone and I have no idea why it wasn't. I am perfectly willing to admit that it was probably a typo on my end that I just did not see.
I ended up embedding the Set and Get within the Singletons class and used it as an intermediary to hold the information I needed. It worked perfectly.
package chart {
import chart.*;
//
public class StaticInstance {
private static var instance:StaticInstance;
private static var allowInstantiation:Boolean;
private var language:String;
public static function getInstance():StaticInstance {
if (instance == null) {
allowInstantiation = true;
instance = new StaticInstance();
allowInstantiation = false;
}
return instance;
}
public function StaticInstance():void {
if (!allowInstantiation) {
throw new Error("Error: Instantiation failed: Use StaticInsance.getInstance() instead of new.");
}
}
public function setLanguage(_language:String):void{
language = _language;
trace("language set = " + language);
}
public function getLanguage():String{
return language;
}
}
}
This code allowed me to hold the data and call upon it again from two different classes. It's a very hack job instead of just being able to pass on the variable from function to function, but in my case we didn't create this file, we are modifying it and attempting to do things beyond the original scope of the project.
Thanks again for your help Shaun! I hope this helps other people!

Get current class+method name in AS3

I wondered if there is a simple way I have have a snippet which traces the name of a method when called. I found className which is half-way there, but not something for the method... a 1-line trace(...) is what I'm after so I avoid typing the method name and leaving myself open to mistakes.
This is for testing the order things happen, when I don't want to step through in the debugger.
If you have compiled your swf with debug information and use the debug version of the player you can take a look at getStackTrace property from the Error object:
Quick example:
public function getCallingInfos():Object{
var tmp:Array=new Error().getStackTrace().split("\n");
tmp=tmp[2].split(" ");
tmp=tmp[1].split("/");
return {namespaceAndClass:tmp[0], method:tmp[1]};
}
var infos:Object=getCallingInfos();
trace(infos.namespaceAndClass, infos.method);
public static function getCurrentClassName(c:Object):String
{
var cString:String = c.toString();
var cSplittedFirst:Array = cString.split('[object ');
var cFirstString:String = String(cSplittedFirst[1]);
var cSplittedLast:Array = cFirstString.split(']');
var cName:String = cSplittedLast.join('');
return cName;
}
Used to check if a certain class is constructed or not.
Usage (here I put the code in the main class):
trace('[DEBUG]: '+ClassData.getCurrentClassName(this)+' constructed.');
trace returns:
[DEBUG]: Main constructed.