1
votes

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:

  1. 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.
  2. 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?

3
Your issue only applies to popups, right? There is no reason your second option wouldn't work with complex data types. What have you tried in that respect that doesn't work? (You might also want to take a look at SkinnablePopUp, a component I created that allows you to create popups in a declarative way - a feature still missing from the Spark component set.RIAstar
Excuse my ignorance, but What is NPE?JeffryHouser
NPE = NullPointerExceptionManius

3 Answers

1
votes

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>
0
votes

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.

0
votes

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));
}