Deferred Component Instantiation in Actionscript

You already know when using a ViewStack (or any ViewStack derivatives) in MXML that you can set the creationPolicy property to determine if it’s children are all created at once, automatically as necessary, or not at all. This is a fairly straight forward procedure in MXML and looks as such:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:TabNavigator x="0" y="0" width="100%" height="100%" creationPolicy="auto">
        <mx:Canvas label="Tab 1" width="100%" height="100%">
        </mx:Canvas>
        <mx:Canvas label="Tab 2" width="100%" height="100%">
        </mx:Canvas>
        <mx:Canvas label="Tab 3" width="100%" height="100%">
        </mx:Canvas>
        <mx:Canvas label="Tab 4" width="100%" height="100%">
        </mx:Canvas>
    </mx:TabNavigator>
</mx:Application>

*In the above code creationPolicy is “auto” by default so including the creationPolicy is redundant.*

However, for you actionscript purists, isn’t it annoying how the addChild in actionscript doesn’t give you the same deferred component instantiation?

I was a bit annoyed today at Flex when I did something similar to the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
     creationComplete="this.onCreationComplete(event)">
    <mx:Script>
        <![CDATA[
            import mx.containers.VBox;
            import mx.events.FlexEvent;
           
            private function onCreationComplete(event:FlexEvent):void
            {
                for(var i:int=0;i<10;i++)
                {
                    var vbox:VBox = new VBox();
                    vbox.percentHeight = 100;
                    vbox.percentWidth = 100;
                    vbox.label = "Tab "+(i+1);
                    this.tn.addChild(vbox);
                }
            }
        ]]>
    </mx:Script>
    <mx:TabNavigator x="0" y="0" width="100%" height="100%" id="tn"/>
</mx:Application>

Naturally you would think, “My TabNavigator has it’s creationPolicy on auto by default, so only the first view will be instantiated.” But when my service calls were being fired for other views I quickly realized this assumption was wrong. Adding a few trace statement my application looked like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
     creationComplete="this.onCreationComplete(event)">
    <mx:Script>
        <![CDATA[
            import mx.containers.VBox;
            import mx.events.FlexEvent;
           
            private function onCreationComplete(event:FlexEvent):void
            {
                for(var i:int=0;i<10;i++)
                {
                    var vbox:VBox = new VBox();
                    vbox.percentHeight = 100;
                    vbox.percentWidth = 100;
                    vbox.label = "Tab "+(i+1);
                    vbox.addEventListener(FlexEvent.PREINITIALIZE,this.onTabPreInitialize);
                    vbox.addEventListener(FlexEvent.INITIALIZE,this.onTabInitialize);
                    vbox.addEventListener(FlexEvent.CREATION_COMPLETE,this.onTabCreationComplete);
                    this.tn.addChild(vbox);
                }
            }
           
            private function onTabPreInitialize(event:FlexEvent):void
            {
                VBox(event.target).removeEventListener(FlexEvent.PREINITIALIZE,this.onTabPreInitialize);
                trace(VBox(event.target).label+" "+event.type);
            }
           
            private function onTabInitialize(event:FlexEvent):void
            {
                VBox(event.target).removeEventListener(FlexEvent.INITIALIZE,this.onTabInitialize);
                trace(VBox(event.target).label+" "+event.type);
            }
           
            private function onTabCreationComplete(event:FlexEvent):void
            {
                VBox(event.target).removeEventListener(FlexEvent.CREATION_COMPLETE,this.onTabCreationComplete);
                trace(VBox(event.target).label+" "+event.type);
            }  
        ]]>
    </mx:Script>
    <mx:TabNavigator x="0" y="0" width="100%" height="100%" id="tn"/>
</mx:Application>

And my trace output was:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Tab 1 preinitialize
Tab 1 initialize
Tab 2 preinitialize
Tab 2 initialize
Tab 3 preinitialize
Tab 3 initialize
Tab 4 preinitialize
Tab 4 initialize
Tab 5 preinitialize
Tab 5 initialize
Tab 6 preinitialize
Tab 6 initialize
Tab 7 preinitialize
Tab 7 initialize
Tab 8 preinitialize
Tab 8 initialize
Tab 9 preinitialize
Tab 9 initialize
Tab 10 preinitialize
Tab 10 initialize
Tab 1 creationComplete
Tab 2 creationComplete
Tab 3 creationComplete
Tab 4 creationComplete
Tab 5 creationComplete
Tab 6 creationComplete
Tab 7 creationComplete
Tab 8 creationComplete
Tab 9 creationComplete
Tab 10 creationComplete

Well this wasn’t going to work at all. I had service calls that I only wanted called on the creationComplete (specifically when a user had clicked to activate the tab). I wasn’t going to change my architecture to include MXML so I had to adjust the actionscript to do what I needed. Luckily the fix is pretty simple (and yet there aren’t any examples of the fix on the web). Here is the final change to make the fix.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
     creationComplete="this.onCreationComplete(event)">
    <mx:Script>
        <![CDATA[
            import mx.containers.VBox;
            import mx.events.FlexEvent;
           
            private function onCreationComplete(event:FlexEvent):void
            {
                for(var i:int=0;i<10;i++)
                {
                    var vbox:VBox = new VBox();
                    vbox.percentHeight = 100;
                    vbox.percentWidth = 100;
                    vbox.label = "Tab "+(i+1);
                    vbox.creationPolicy = "none";
                    vbox.addEventListener(FlexEvent.PREINITIALIZE,this.onTabPreInitialize);
                    vbox.addEventListener(FlexEvent.INITIALIZE,this.onTabInitialize);
                    vbox.addEventListener(FlexEvent.CREATION_COMPLETE,this.onTabCreationComplete);
                    this.tn.addChild(vbox);
                }
            }
           
            private function onTabPreInitialize(event:FlexEvent):void
            {
                VBox(event.target).removeEventListener(FlexEvent.PREINITIALIZE,this.onTabPreInitialize);
                trace(VBox(event.target).label+" "+event.type);
            }
           
            private function onTabInitialize(event:FlexEvent):void
            {
                VBox(event.target).removeEventListener(FlexEvent.INITIALIZE,this.onTabInitialize);
                trace(VBox(event.target).label+" "+event.type);
            }
           
            private function onTabCreationComplete(event:FlexEvent):void
            {
                VBox(event.target).removeEventListener(FlexEvent.CREATION_COMPLETE,this.onTabCreationComplete);
                trace(VBox(event.target).label+" "+event.type);
            }  
        ]]>
    </mx:Script>
    <mx:TabNavigator x="0" y="0" width="100%" height="100%" id="tn"/>
</mx:Application>

Notice that all I did was add the creationPolicy not to the TabNavigator, but to the VBox that I was adding as my container. This one line made all the difference. And now at startup my trace looks like:

1
2
3
4
5
6
7
8
9
10
11
12
Tab 1 preinitialize
Tab 2 preinitialize
Tab 3 preinitialize
Tab 4 preinitialize
Tab 5 preinitialize
Tab 6 preinitialize
Tab 7 preinitialize
Tab 8 preinitialize
Tab 9 preinitialize
Tab 10 preinitialize
Tab 1 initialize
Tab 1 creationComplete

Just the way we originally intended. Hope that helps!

  • Share/Bookmark

Comments (7)

Hussein GrantJune 20th, 2009 at 3:09 am

THANK YOU!! THANK YOU!!

You saved me hours!!! of debugging. I spent about 2 hours already trying to solve this problem.
Thank you again. :)

Devin ReimerJuly 22nd, 2009 at 4:13 pm

Thank you sooo much, I’ve been fighting with this issue for 3 hours, it never occurred to me that setting the creation policy to none on the individual components would be neccessary. After reading at least 50 blogs, finally I discovered yours.

Thanks

NeilSeptember 28th, 2009 at 5:07 am

Great post, one thing I would like to know. If you are not using a TabNavigator but some other container, how would one go about instantiating the children using createComponentFromDescriptor as it seems that only MXML components are in this array, not the ones created dynamically.

Thanks

Jonathan CamposSeptember 28th, 2009 at 8:53 am

The process of deferring component instantiation within a container and for a viewstack is a bit different but with the same goal, improved performance.

Neil, for your question let’s use the example of having a really long page with multiple containers. It’s stupid to instantiate components that are off the screen, so we can wait till the scroll bar or view port gets to a specific value and then start to instantiate the container’s children components.

This would be a good use of the createComponentFromDescriptor or createComponentsFromDescriptors. Then when your case is hit you can call vbox.createComponentsFromDescriptors(true); Now all the children components that you originally set the creationPolicy to ContainerCreationPoicy.NONE to be created.

That help?

ribaMay 4th, 2010 at 3:13 pm

You da man! That was exactly what I was looking for!

Pie21May 23rd, 2010 at 3:37 am

Great catch!

In my case, I have a main ViewStack (of VBoxes) controlled by a LinkBar. I have two states, before and after the user logs in. In the ‘before’ state they can only see the home page, but in the ‘after’ state, they can access all the pages.

The correct solution then is to only have the home page on the view stack initially, and then add the other pages once the user logs in. The problem was that adding the pages would cause them to load all their children, so when the state changed, all the non-home page pages would be completely created.

Without states, deferred instantiation worked as expected, and with this fix it now works as expected WITH states as well. Each page sets its creationPolicy=”none” (in the first (VBox) tag of the MXML definition), and it is loaded by the ViewStack container when the user switches to it.

Again, bravo!

AshineJuly 19th, 2010 at 11:35 am

Thats a superb post.
Thanks for sharing these with us.
I have one more doubt on same lines, I have posted it on Stackoverflow, can you plz help me here ? Link
“http://stackoverflow.com/questions/3265411/difference-between-array-and-array-collection-in-flex/3265732#3265732″

Leave a comment

Your comment