Delayed Instantiation is a technique in Flex whereby children of IContainer components aren't created until the user actually needs to see them. For instance, if you know that only the top half of your component will be showing, you can take control of the creation of children added to the component through MXML and only show the ones you know they will see at first. Then, when they scroll, you could show the rest.
The most familiar example of a Flex component that uses delayed instantiation is ViewStack and its subclasses, Accordion and TabNavigator. Under most circumstances, the creationPolicy on a ViewStack will be "auto", which means that the direct children of the ViewStack will be created immediately, but in most cases the grandchildren will be created the first time the particular ViewStack "pane" is shown. Usually, this process is fairly painless, but recently I've encountered "gotchas" in two situations, which are: when the ViewStack contains a Container subclass that needs to set properties on its children and when the ViewStack's children are created by a Repeater and those children contain a List-based component.
This week, I'll talk about the first of those situations, and I'll cover the Repeater example next week. The solution to this problem is fairly simple, so if you're up against a deadline and don't want to wade through the explanation of why it works, jump to the solution now.
Subclassing Container isn't all that difficult–you just need to use Canvas, Box, Vbox, etc. as your superclass in an AS class or as the root tag in MXML. For simplicity, I'm going to refer to a hypothetical extended VBox called VBoxWithComboBoxes. Now, comboboxes need a fair amount of setup. First, they need a dataProvider to give you some choices. That's relatively easy to do with data binding. However, the value that determines what's selected in the combobox seldom maps exactly to any of the items in the dataProvider, so usually you have to loop through the dataProvider and find the item that matches your criteria, then set selectedItem or selectedIndex based on that match.
And this leads to a problem, because you need to do this whenever the value that determines what's selected changed or whenever the dataProvider changes. Without getting too much into the component lifecycle, we don't have any guarantees that the ComboBox actually exists when the setters for these values are called, so most developers call invalidateProperties() in the setter and set a flag that we then look at in a commitProperties() override and then make the change there. Usually this works, because commitProperties() is always called after createChildren().
But if you're like me and you assume that, at the end of createChildren() all of the children have been created, you'd be wrong, because of course when a container is created with delayed instantiation, the children likely haven't been created. So then the challenge becomes one of determining whether or not the children have been created yet.
My preliminary research showed me that the children definitely get created by the end of initialize(), so my first thought was to check initialized in commitProperties(). There was just one problem with that, which is that the invalidation flag that got set when I called invalidateProperties() in the setters had already cleared by the time initialization had finished, and so commitProperties() wasn't getting called. So I needed to find a more surefire method of making sure I wasn't trying to access my children before they were created.
To do this, I had to dig into the code of Container and ViewStack, so I thought I'd summarize what happens.
- In UIComponent, which is the base class of Container, initialize() calls createChildren().
- Container overrides createChildren() and calls a new function, createComponentsFromDescriptors(). This takes a flag that tells it whether or not to recurse its children, and when this is called from within Container, that flag is always true. At the end of this function, it sets a variable, processedDescriptors. This variable originates as a setter in UIComponent. In addition to setting the underlying private variable, this setter generates an event that notifies everyone that the container is initialized.
- ViewStack overrides createComponentsFromDescriptors to check the creationPolicy and set the recurse flag based on what's appropriate for the creationPolicy. At the end of this override, the processedDescriptors variable is also true, but keep in mind that this is only the processedDescriptors variable for the ViewStack itself (though of course it will also be true on the component that's on the first pane as well). What this means is that if our VBoxWithComboBoxes is a child of a ViewStack, but not on the first pane, its children will likely not be created (we'll talk about the exception next week), and its processedDescriptors variable will be false, until the ViewStack tells it to process its child descriptors.
- ViewStack has another function, instantiateSelectedChild(), which is called, from among other places in ViewStack, in commitProperties() when the selectedIndex setter sets up conditions which might result in the selection of a child that hasn't been instantiated. This function will then call createComponentFromDescriptors() with the recurse flag set to true, which will then tell our VBoxWithComboBoxes to create its children, after which the processedDescriptors instance variable is set to true on the shiny new VBoxWithComboBoxes that's been created.
After doing all of this research, it was pretty clear to me that the variable I needed to be looking for was processedDescriptors. However, I still wasn't sure what to override to persuade commitProperties() to run. My first choice was initialize(), because its simpler method signature meant there was less likelihood that I would screw anything up while calling super.initialize(). However, it appears that the compiler frowns on overriding initialize() in a Container subclass, so I wound up overriding createComponentsFromDescriptors(), only to add a call to invalidateProperties().
So, together with my commitProperties override, the code to resolve this issue looks like this:
override public function createComponentsFromDescriptors(recurse:Boolean=true):void {
super.createComponentsFromDescriptors(recurse);
/* This makes sure that, now all our components are created,
we can assign properties to them.
*/
invalidateProperties();
}
override protected function commitProperties():void {
if (processedDescriptors) {
if (_comboSelectedPropertyChanged || _comboDataProviderPropertyChanged) {
childCombo.dataProvider=_comboDataProviderProperty;
//logic to find the selected item based on properties of the items in the dataProvider
//init flags
_comboSelectedPropertyChanged =false;
_comboDataProviderPropertyChanged=false;
}
}
super.commitProperties();
}
Tune in next week to see how to resolve issues caused by delayed instantiation of List-based containers created by a Repeater.