MXML-class initialization order - apache-flex

I have written a few custom components in Flex 4, and run into this issue a few times.
var myForm:MyForm = new MyForm;
myForm.SetData(data);
addElement(myForm);
Now Imagine I am calling these functions from a non-constructor function of a Panel or VGroup (or any other container). Annoyingly, during MyForm.SetData(), not all fields of myForm that are declared there are yet initialized. Such as:
<s:VGroup id="dataGroup">
If my SetData()-function wants to access dataGroup (for the reason to .addElement() the just recieved data to it), it just fails with a nullpointer exception, because dataGroup is not yet created, despite this being after the constructor. How can guarantee that the form was initialized fully?

Listening for the creationComplete event and adding your components in the handler for the event is one way to do this. This is what Sam DeHaan suggested.
Another way you can do this is to override the createChildren() function. This is the function that creates and adds all of a component's child components. Code would look something like this:
override public function createChildren():void
{
super.createChildren();
var myForm:MyForm = new MyForm;
// Note that data may be null here, best to
// override commitProperties() to set it.
myForm.SetData(data);
addElement(myForm);
}
The docs on the component lifecycle will provide a ton of detail on this topic.

Unless I'm misunderstanding your question,
You should put the code that runs into this null pointer exception in a creationComplete callback on the container that you need defined.

Related

Using singleton WCSession delegate instead of instance methods

I'm experiencing a strange issue with WatchOS (but I suppose that this problem is similar with iOS and OSX).
I'm using a singleton to handle a WCSession delegate (The full code is by NatashaTheRobot, I paste here only a portion of her code, the full code is here ).
This class has a startSession function where the singleton is associated as delegate of the session:
func startSession() {
session?.delegate = self
session?.activateSession()
}
and all the delegate functions are defined inside the same class, like session:didReceiveMessage:replyHandler:
I'd like to be able to have the delegate called every time that the Watch app receives a message independently by the current InterfaceController.
I thought that a good place to achieve this goal might be the ExtensionDelegate class:
class ExtensionDelegate: NSObject, WKExtensionDelegate {
let session = WatchSessionManager.sharedManager // THE SINGLETON INSTANCE
func applicationDidFinishLaunching() {
session.startSession()
}
it seems that this code is not working and the delegate function are never called.
Then I decided to go for a less generic way and I started adding the reference to the singleton instance inside all the InterfaceController... but again it doesn't work and delegate methods are never been called.
Then, in my last attempt, I've implemented the session delegate protocol directly inside the InterfaceController code. In that case I receive the messages from the iOS app... it was working correctly (obviously only when the watch app is presenting that specific InterfaceController).
My question are: why implementing a generic singleton object doesn't work? Why I have to implement the delegate directly on the InterfaceController to make it work?
Try moving the startSession call from the ExtensionController's applicationDidFinishLaunching to its init method. The init gets called no matter which context (complication, app, glance, notification, etc) the extension is being loaded for.

Flex: NPE and Proper way to access component properties before creation complete

I think I'm missing something important about flex sdk component lifecycle, but cannot sort it out, although read a lot of tutorials. Could you please share your experience on how do you operate with flex visual object's properties and how do you avoid NPE when accessing them before component creation complete.
Let's say we have a simple component MyTitleWindow.mxml:
<s:TitleWindow>
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Another component got data from a remote object, and wants to apply the data into title window's datagrid and show it via PopUpManager:
private function handleDataReceived(data : ArrayCollection) : void {
var myTitleWindow : TitleWindow = new MyTitleWindow();
PopUpManager.addPopUp(myTitleWindow);
myTitleWindow.myDataGrid.dataProvider = data;
}
Ofcourse, the line myTitleWindow.myDataGrid.dataProvider = data will throw an NPE because we're trying to access myDataGrid that haven't been rendered yet.
Currently I can see only 2 options how to avoid NPE:
Create a setter for data in titleWindow, put the data into some
cache. Listen to creationComplete event, in it's handler apply data
from the cache to the datagrid. This approach works fine, but I'm
tired of adding this safe-guards across the application.
Make a
bindable property - works for me only with simple datatypes
(numbers, strings...)
Is there anything I'm missing in the area of using flex validation/invalidation cycle that could help to avoid excessive code?
The problem you are having and Crusader is reporting is because in Flex components are initialized lazy. This is generally a good thing but in your case it is what's causing your problems.
In general I wouldn't suggest to set the dataProvider on a view component from outside the component as you have no way of knowing if all is setup and ready to use.
What I usually do. In simple cases I simply add a public propberty which I make [Bindable]. The (i think) cleaner way would be to create a setter (and getter) and so save the dataProvider in a local variable (in your case probably ArrayCollection). In the setter I usually check if the "myDataGrid" exists and if it exists, to additionally set the dataProvider property. I would then add a CreationComplete callback in my component and in that I would also set the dataProvider.
So when setting the dataProvider before the component is finished initializing, the value would simply be saved in the local variable and as soon as it is finished setting up the dataProvider is automatically set. If the component is allready setup (you are changing the dataProvider) the setter would automatically update the dataProvider of "myDataGrid"
<s:TitleWindow creationComplete="onCreationComplete(event)">
...
private var myDataProvider:ArrayCollection;
private function onCreationComplete(event:FlexEvent):void {
myDataGrid.dataProvider = myDataProvider;
}
public function set myDdataProvider(myDataProvider:ArrayCollection):void {
myDataProvider = myDataProvider;
// Only update the dataProvider if the grid is available.
if(myDataGrid) {
myDataGrid.dataProvider = myDataProvider;
}
}
....
<s:DataGrid id="myDataGrid" />
</s:TitleWindow>
Yeah, this can be an annoyance, and it's not just with popups. It can also be a pain when using a ViewStack components with a default creationpolicy, which I tend to do fairly often.
I might get flamed for this, but I usually just use bindings. I'm not sure what you mean by "simple datatypes" - it works fine with custom types too. You'd have to provide an example.
One thing you could do (and I'll probably get flamed for this :p ) is create your popup component instance early on and re-use it rather than creating a new one each time.
No, I don't think you're "missing something" in the component lifecycle.
I always try to invert the responsibility for setting a dataProvider and prefer to have components observe a [Bindable] collection.
In simple examples such as yours I avoid giving my components an id. This prevents me from breaking encapsulation by referring to them externally.
<s:TitleWindow>
<s:DataGrid dataProvider="{data}" />
</s:TitleWindow>
The consumers of your MyTitleWindow component should not know that it has a DataGrid with id 'myDataGrid', or be required to set the dataProvider property of 'myDataGrid'.
Considering components declared in MXML require a no-arguement constructor (and that we're unable to declare multiple constructors) - one approach that has worked well for me in the past is to offer a static 'newInstance' method. I give this method a relevant name based on the domain I'm working in, and also any required parameters.
public static function withData(data : ArrayCollection) : MyTitleWindow
{
var myTitleWindow : MyTitleWindow = new MyTitleWindow();
myTitleWindow.data = data;
return myTitleWindow;
}
This clearly communicates the 'contract' of my component to any and all consumers. (Obviously things become clearer with more relevant naming).
private function handleDataReceived(data : ArrayCollection) : void
{
PopUpManager.addPopUp(MyTitleWindow.withData(data));
}

Passing data and calling functions between host component and renderers in Flex

Is there any better way for passing data between component and its renderers (also calling methods)?
I am using dispatchEvent(new CustomEvent(CustomEvent.SOME_EVENT, data)); or owner.dispatchEvent(new CustomEvent(CustomEvent.SOME_EVENT, data)); or bubbling (depends on situation). And then listening in that component for my event.
And for passing data to renderer, I am using:
(myList.dataGroup.getElementAt(myList.selectedIndex) as MyRenderer).doSomething(data); or modifying dataProvider but then I have to callLater and call method inside renderer anyways.
This sucks, I have to callLater all the time for to wait for renderers set data function to execute, otherwise renderer is not recycled yet and is null.
Is there any more efficient, prettier way to transfer data and call functions between renderer and component?
P.S. And yes I have pass data to renderer. My renderer handles quite complicated logic.
I've resolved a problem similar to yours in this way:
var goalPropDLRenderer:ClassFactory =
new ClassFactory(GoalPropagationDataListRenderer);
goalPropDLRenderer.properties = { isPropagationMode : isPropagationMode };
list.itemRenderer = goalPropDLRenderer;
Where: "list" is a list object for witch I've to set an item renderer; instead of setting the item renderer inline (on the "list" declaration via mxml), I've used this syntax. This code is called on the creationComplete of the "list" father (I think there are better places) In this way it's possible to pass "properties"/"values" to the renderer.
On GoalPropagationDataListRenderer I've:
[Bindable]
public var isPropagationMode : Boolean;
that gets populated by goalPropDLRenderer.properties = { isPropagationMode : isPropagationMode }; so inside the renderer I can access the value of isPropagationMode without problems.
I hope it could help.
There are some ways, depends on what exactly you want to do (if the data are dependent to every row, or global in datagrid), but personally, I would avoid callLater and this you wrote: (myList.dataGroup.getElementAt(myList.selectedIndex) as MyRenderer).doSomething(data)
you can use bubbling event, then catch it in datagird (or whenever), and then get renderer through myRenderer(event.target), not needed to use getElementAt!, but questin is when would you dispatch the event (I dont know the details)
if you use global (in datagrid meaning) logic:
I would make some common object (called logic) which will be set in datagrid and renderers (separating logic from view is good practice anyway in my opinion)
then I would use the solution what Claudio wrote (setting properties to itemRenderer ClassFactory) to set the logic to renderer
or you can use listData object (but it's not as simple)
or if you write item renderer in mxml inside datagdid, you can easilly bind it using outerDocument like logic="{outerDocument.logic}"
or you can just use itemRenderer's owner property, so whenever yo want some methods, call like MyDataGrid(owner).doSomeDataGridCalculationsForData(data), not needed to create logic object if you dont want to

Flex adding event listeners with callback functions which have no arguments

fileReference.addEventListener(Event.COMPLETE, uploadCompleteHandler);
private function uploadCompleteHandler(event:Event):void {}
Above is one way to add an event listener in Actionscript.
By default the callback function needs to have an argument with
name event and type Event.
Is there a way to declare this function without any arguments :
private function uploadCompleteHandler():void {}
Edit : It's possible to add an event handler without any arguments in mxml. So one student wanted to know, why isn't it possible to do the same in actionscript?
The reason is that in mxml what you write isn't actually the handler, is what gets executed in the handler. If you compile with -keep-generated-actionscript flag (To set it in Flex Builder right click to open the project Properties, select Flex Compiler, and add -keep-generated-actionscript to the Additional compiler arguments), you can see in the generated source for your component, that the compiler created a handler for that event, and the body is composed by that you wrote in mxml.
So if you have something like:
click="doSomething();"
You can already notice that you're actually giving an instruction there, that's not a method reference you're passing like when you use addEventHandler.
Then you'll have in the generated file something like:
private function myComponent_Click(evt : MouseEvent) : void
{
doSomething();
}
And somewhere else in the same file the adding of the event listener:
this.addEventListener(MouseEvent.CLICK, myComponent_Click);
Notice the second parameter is not a function result, it's a function reference, because the parenthesizes that denote a function call are missing, and our particular function is not a getter either.
You can also specify more calls in mxml, like:
click="doSomething(); doSomethingElse();"
You can even pass the event parameter to your method:
click="doSomething(event);"
Whatever you write in the value of the mxml event (not sure it's the right term to use) will become the body of the generated handler for the actionscript event.
No, you'll get a runtime exception when the event fires if you'll try to register it as event listener.
You may use the following syntax to allow calling your handler directly without event object:
private function uploadCompleteHandler(event:Event = null):void {}

Flex 3 - Must I add components before setting their attributes when using AS3?

Let us say that I have a Flex 3 mxml component, call it A. A has a get/set attribute called 'b'. Within A I have another internal component C, which is specified using mxml. When "instantiating" component A within mxml, I can specify the value of b at declaration, and everything works fine. However, when I initialize the component using Actionscript, I must first add the component to a rendered container before I can set the attribute (in this case 'b') of said component. This happens when the setter for attribute 'b' somehow accesses C within A.
So, this fails at runtime (it says that C is null)...
var a:A = new A();
a.b = "woopy"; //Sets the Label (declared in mxml) withn A to "woopy"
this.addChild(a);
On the other hand, either of the following will work
<customNamespace:A b="woopy"/>
or
var a:A = new A();
this.addChild(a);
a.b = "woopy"; //Sets the Label (declared in mxml) withn A to "woopy"
As shown, no runtime error message is thrown when a attribute is set after a component is added to a container. Ok, this makes sense, I suppose the internals of the component are not actually created until the component is added to a container. Still, this is kind of annoying. Is there any way to guarantee that the component internals are fully rendered without adding it to a container? I don't like the way it feels different when I am using actionscript vs mxml. I want a solution so that basically declaring A in mxml with no attribute "arguments" is equivalent to declaring A using the new operator in AS. At least, in terms of the internal state of A.
To force a control to create its child controls you have to call the initialize method.
i.e. this should work :
var a:A = new A();
a.initialize();
a.b = "woopy";
this.addChild(a);
However, what I've been doing so far when declaring mxml controls is binding the internal controls to public variables declared in a script block. e.g.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
[Bindable]
public var labelText:String = "[Default]";
]]>
</mx:Script>
<mx:Label text="{labelText}"/>
</mx:Canvas>
This way you can set your parameters without having to worry about whether the controls have been created or not.
That's right -- if B's setter acts on C, you'll have problems, because when A's constructed, C's definitely not there yet, even if you've declared C in A's MXML.
About there being any way to guarantee a component and its children are fully rendered and usable without adding it to a container, the answer is no -- the framework won't perform its creation and rendering magic on a component until it's somehow added to the display list, either by way of MXML or addChild().
Of course, you could use the visible or includeInLayout properties (setting both to false on your A component, for example) to get around actually displaying the component, or if you had to do the instantiation in script, you could listen for either A's initialize or creationComplete events (both of which indicate A's children have been created and are ready to be acted upon), and just wait to set B until you receive that notification. As a general rule, though, I wouldn't advise calling the initialize() method directly; it's a framework method that gets called automatically just after addChild() anyway, and in general it's better to let the framework do its thing, rather than work around it.
var a:A = new A();
a.addEventListener(FlexEvent.INITIALIZE, a_initialize);
addChild(a);
private function a_initialize(event:FlexEvent):void
{
a.b = "woopy";
// ... and so on
}
So it goes, if you want to be sure.
Deepa Subramaniam, an engineer on the Flex team, recently posted an excellent video on her site covering the Flex component model in granular, step-by-step detail; I attended the talk at MAX where she recorded it, it was easily one of the best of the conference. Worth watching (and re-watching, and then watching again) for its detail and comprehensiveness. She actually addresses your question at several times during the talk. It's great stuff.
Best of luck!
To answer your main question, no, you don't have to add an AS3-instantiated component to the display list if you want to set its properties. There's no difference between creating it in MXML versus creating it in AS3... unless, of course, the component wasn't built properly.
The Flex team at Adobe (formerly Macromedia) spent many years refining optimizations for the Flex component architecture. There are two important parts of that design that are related to your problem:
They designed system of invalidation and validation so that you can set many properties at once, but the effects of the changes don't happen until you're done making all your changes.
When a component is first instantiated, it's children are not created right away. There's an optimal time to do it, and that's after the component has been added to the display list.
Basically, when you have a difference in behavior between an MXML instantiated component and an AS3 instantiated component, it's because the component was built without these two features in mind.
The component that is behaving improperly probably does something like this:
private var label:Label;
public function get b():String
{
return this.label.text;
}
public function set b(value:String):void
{
this.label.text = value;
}
The problem is that the component developer didn't take into account that the Label sub-component may not have been created yet! The best practice is to save the value in a variable and invalidate to pass it to the sub-component later (the validation cycle doesn't happen until after the component is initialized and the children are created).
private var label:Label;
private var _b:String;
public function get b():String
{
return this._b;
}
public function set b(value:String):void
{
this._b = value;
this.invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
this.label.text = this._b;
}
Alternatively, if you build an MXML component, you can do something similar, but it's often easier to use binding instead of the validation system:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="{this.b}"/>
<mx:Script><![CDATA[
private var _b:String;
[Bindable]
public function get b():String
{
return this._b;
}
public function set b(value:String):void
{
this._b = value;
}
]]></mx:Script>
</mx:Application>

Resources