Component initialization order
Roku's documenation contains a good article about component initialization order. Specifically, it states:
Instances of components defined in an XML file follow a well-defined initialization order when they are created.
<children>element nodes defined in XML markup are created, and their fields are set to their initial values, either to a default value, or to the value specified in the XML markup.
<interface>element fields of the XML component are created, and their initial values are set, either to a default value, or to the value specified by the value attribute.
init()function is called, and all initializations contained in the function are performed.
While that is very helpful, it leaves out some subtle bits and sometimes it can be hard to visualize, so let's do some experiments to better illustrate the order.
Consider the following component hierarchies:
Parent > ParentBase > Group Child > ChildBase > Group
Let's let the
ParentBase component define a
Child in its
<?xml version="1.0" encoding="utf-8" ?> <component name="ParentBase" extends="Group"> <script type="text/brightscript" uri="pkg:/components/ParentBase.brs"/> <children> <Child id="childInParentBase"/> </children> </component>
And, let's imagine that the
Parent component defines a
Child component as part of its
<?xml version="1.0" encoding="utf-8" ?> <component name="Parent" extends="ParentBase"> <script type="text/brightscript" uri="pkg:/components/Parent.brs"/> <children> <Child id="childFromParent"/> </children> </component>
Now consider another component that uses the
Parent component and defines some additional children:
<?xml version="1.0" encoding="utf-8" ?> <component name="OtherComponent" extends="Group"> <script type="text/brightscript" uri="pkg:/components/OtherComponent.brs"/> <children> <Parent id="parent"> <Child id="firstchild"/> <Child id="secondchild"/> </Parent> </children> </component>
If you run this code, you will see that the order of the
init calls is actually:
ChildBase for childInParentBase Child for childInParentBase ParentBase for parent ChildBase for childFromParent ChildBase for childFromParent Parent for parent ChildBase for firstChild Child for firstChild ChildBase for secondChild Child for secondChild
So if we wanted to write a more complete order of initialization, it might look like this:
- If the component extends another component, the base component (including all children) will be initialized first
- Initialize nodes defined in
- Call component's
- Field values are available for reading
- Children added via markup are initialized
Understanding the order is helpful, but understanding the implications is also very important. Consider these perhaps subtle implications:
Non-default field values are not available in
sub init() ?"My id is: " + m.top.id end sub
id property was set in markup, you might be surprised to see the output of this is:
My id is:
At the time that
init() is called, any non-defaulf field values are not yet set, so
m.top.id has no value.
Similarly, any values you set on
init() could be overwritten when initialization is complete:
sub init() m.top.id = "set_in_init" ?"My id is: " + m.top.id end sub function onKeyEvent(key, press) ?"My id is now: " + m.top.id return true end function
My id is: set_in_init My id is now: parent
The Roku docs say:
For nodes that are defined in the
<children>XML markup of the component file, the parent node is set after the node is created, and
Essentially that means that the children are not yet 'parented' when their
init() function is ran. That means that if you call
init(), it will return
Following on the above behavior, that means that the children created in a component are not yet valid targets for focus while
init() is running. The children are not parented (added to the Scene Graph tree) until after
init() is complete and calls to
setFocus() require that the node being focused is in the Scene Graph tree. However, experimentation has shown that you can often get away with calling
setFocus() on a child in the
init() and I personally have done it often in real apps. I have also seen it not work and struggled to figure out why, so it is probably better to just avoid the ambiguity and not do this.
(It is usually not a good idea to set the focus during object creation anyway, and a better approach is to set up an observer on
focusedChild so that your component is aware when something is trying to set focus on it and handle focus at that time.)