Setter isn't always triggered - actionscript-3

I use a bindable variable that represents a Model and pass it down thru several nested components.
Top level component is my variable:
[Bindable]
private var meetingInfo:MeetingInfoModel;
I initialize it in the handler for the "preinitialize" event:
meetingInfo = MeetingInfoModel.getInstance();
I then pass it thru to 1 component:
<meetingViewStack:MeetingViewStack meetingInfo="{meetingInfo}"/>
In that component I have the following:
private var _meetingInfo:MeetingInfoModel;
public function set meetingInfo( model:MeetingInfoModel ):void{
_meetingInfo = model;}
[Bindable]
public function get meetingInfo():MeetingInfoModel{
return _meetingInfo;
}
I then pass that variable to another nested component:
<documentShare:DocumentPanel meetingInfo="{meetingInfo}"/>
Where I have the same setter/getter set up. Then I pass it again to another nested component:
<documentShare:AttachmentFilesPanel meetingInfo="{meetingInfo}" />
In that component I have an ItemRenderer for a DataGroup component where it binds to "meetingInfo":
<s:DataGroup id="attachmentsList"
width="100%"
clipAndEnableScrolling="true"
dataProvider="{meetingInfo.docsAndAttachmentsList}"
itemRenderer="com.fmr.transporter.view.documentShare.DocumentUploadRenderer"
visible="{meetingInfo.docsAndAttachmentsList.length > 0}">`enter code here`
As I update the "meetingInfo.docsAndAttachmentsList" ArrayCollection, the setters in each nested component get called correctly, until....
...and this is an AIR app, mind you...
I log out and back in. Then, the setter/getter in the last nested component is not fired this time.
Why, oh why, would this happen?
I've been troubleshooting for a couple days and cannot figure it out for the life of me.
Thanks for any helpful tips!!

The code MeetingInfoModel.getInstance(); hints that MeetingInfoModel is a singleton, is it? If so, setter will be triggered only once, because of the mxmlc generates the check that objects instance changed, the generated code for SDk 4.5.1 will be like this:
[Bindable(event="propertyChange")]
public function set meetingInfo(value:MeetingInfoModel):void
{
var oldValue:Object = this.meetingInfo;
if (oldValue !== value)
{
this._883716727meetingInfo = value;
if (this.hasEventListener("propertyChange"))
this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this, "meetingInfo", oldValue, value));
}
}
so if you didn't recreate instance of MeetingInfoModel the setter will not be triggered.

Related

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.

Trigger an Event when value has changed

I'm trying to trigger an event (function) in an air application when a value has changed (i.e. when a user has logged in), but so far I've no succeeded in triggering my function...
I have following code:
A user class:
[Bindable]
public class User
{
private var m_rssLink:String;
public function get rssLink():String{
return m_rssLink;
}
public function set rssLink(value:String):void{
m_rssLink = value;
}
}
A header component with id header where the user logs in and the user is defined:
<fx:Declarations>
<user:User id="user" />
</fx:Declarations>
And finally the mxml code with the actionscript:
private var loginWatcher:ChangeWatcher;
protected function creationCompleteHandler(event:FlexEvent):void
{
this.loginWatcher = ChangeWatcher.watch(header.user, ["rssLink"], sendRequest);
}
I've tried to trigger the sendRequest function in a lot of ways by now (with [bindable (event="")], with a bindsetter,... ) and so far Google brought me no other options.
Anyone sees where my code fails, or knows a better way to accomplish this?
Well, you have already made a setter property for User. Use that to track user states.
public function set rssLink(value:String):void{
if (m_rsslink==value) return;
m_rssLink = value;
dispatchEvent(...); // use correct event(s) for certain values, then catch
// them in your watcher class. Use FlexEvents if you want.
}

movie clip class parameters ane null

I have a movie clip with an external class attached.
here is the MC code (I've shorten it only for the relevant part...)
package {
//all the imports here...
public class mc_masterChapter extends MovieClip {
public function mc_masterChapter() {
trace (picFile,strChapTitle);
}
//Properties
public var picFile:String;
public var strChapTitle:String;
}
}
In the main class file I'm adding this object to stage using addChild:
var masterChapter:mc_masterChapter = new mc_masterChapter;
masterChapter.picFile = "pic_Chap1.jpg";
masterChapter.strChapTitle = "ABCD:
addChildAt(masterChapter,1);
now, the trace in the MC class code gives nulls to both parametes but if i put a trace inside the MC timeline (instead of the attached class code), it gives the right value!
how can I access the values from the MC class itself without getting nuls?
Thank you.
It works! Let me explain:
var masterChapter:mc_masterChapter = new mc_masterChapter; // Calls class constuctor
// so calls trace() too!
// You will get null null
masterChapter.picFile = "pic_Chap1.jpg"; // Assign the variables
masterChapter.strChapTitle = "ABCD"; // so they can be read
trace(masterChapter.picFile, masterChapter.strChapTitle); // Should trace pic_Chap1.jpg ABCD
If you add the following method to your class:
public function test():void {
trace(picFile, strChapTitle);
}
Then call masterChapter.test() it will successfully trace those two properties. So yes, the class can read its properties.
Make the var you use in your main class public static vars.
OK!
I solved the mystery.
I put two traces. one in the main MC class saying "hey, I'm inside the MC - the picFile="
and one in the put Function saying "I'm putting this file into picFile:"
well this is what I've got:
hey, I'm inside the MC - the picFile=null
I'm putting this file into picFile:image.jpg
got it!?! at the moment I asked him to give birth to an instance of the MC (even before putting it on stage - just defining the object (with this line:)
var masterChapter:mc_masterChapter = new mc_masterChapter;
it allready run the class, so of course that in this stage the parameters were not defined allready and were null.
the definition code came right after that line (in the main.as)
masterChapter.pic="pic_Chap1.jpg";
so what I did, was to move all the code from the main class of the MC object to a public function inside the same package called init(). Then I called this function manually from the parent main class.
By that I can decide when to call it (after I declare all the parameters of course).
That's it.
god is hiding in the small details : )
tnx for all the helpers.
Possibly a better solution would be to use a getter/setter pair, so you can know at the exact moment the properties are set:
protected var _picFile:String:
public function get picFile():String {
return _picFile;
}
public function set picFile(value:String):void {
if (value != _picFile) {
_picFile=value;
trace('picFile set to', _picFile);
}
}

AS3/Flex - persistNavigatorState + objects with ArrayCollection()

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.

Creating Dynamically Flex Custom ItemRender (Constructor)

am creating some Advanced Datagrid with actionscript.
I have created an actionscript class where I extend the VBox object:
package core
{
import mx.containers.VBox;
import mx.controls.TextInput;
public class customItemRender extends VBox
{
public function customItemRender(_TextInput:TextInput, _TextInput2:TextInput)
{
//TODO: implement function
super.addChild(_TextInput);
super.addChild(_TextInput2);
}
}
}
The problem comes up when I declare de itemrender property on the data grid:
AdvancedDataGridColumn.itemRenderer = new ClassFactory(customItemRender(_TextInput1,_TextInput2));
The compiler wont let me instanciate my customItemRender.
Does any one know if there is an alternative solution to solve the problem?
Thanks in advance for you helps,
Regards Javier
private var _ItemRendere:ClassFactory;
private function get MyItemRendere():ClassFactory
{
if (_ItemRendere == null)
{
_ItemRendere = new ClassFactory();
_ItemRendere.generator = customItemRender;
_ItemRendere.properties = {
_TextInput1:MY_TextInput1_OBJECT,
_TextInput2:MY_TextInput2_OBJECT
};
}
return _ItemRendere;
}
then you can use
AdvancedDataGridColumn.itemRenderer = MyItemRendere;
I've only tried to do this using MXML. In that case, i usually have to wrap the IListItemRenderer instance in mx:Component tags. I'm not exactly sure what is going on programmatically when I do this, but it works. The reason is that the itemRender is actually looking for an instance of IFactory rather than an instance so I suppose to do this strictly using AS you would need to create your own IFactory implementation.
e.g.
<mx:List>
<mx:itemRenderer>
<mx:Component>
<mx:Text />
</mx:Component>
</mx:itemRenderer>
</mx:List>
ClassFactory's constructor has a Class as a parameter, not an instance. You need to call:
new ClassFactory(customItemRender);
and not:
new ClassFactory(new customItemRender(_TextInput1,_TextInput2));
or:
new ClassFactory(customItemRender(_TextInput1,_TextInput2));
Now, since the constructor will not be called with reference to TextInput1 and TextInput2, you'll need to instantiate your own TextInputs in the custom renderer itself. (But this is a good thing, if you continue to call new customItemRender(_TextInput1, _TextInput2), then the two TextInputs will only be added to the LAST instance of customItemRender, and all of the others will not have these two objects ).