Flex Custom Component Lifecycle

The purpose of this post is to identify what the component lifecycle is and how to utilize it to create components that are efficient and re-usable. I will cover both the functions we need to override as well as the events that are dispatched along the way.

First, let’s start with a quiz.

1. True or False. I know what the lifecycle functions: createChildren(), commitProperties(), measure(), and updateDisplayList() are and I use them often in my custom component development.
2. True or False. I know what circumstances are appropriate for overriding commitProperties(), measure(), and updateDisplayList().
3. True or False. I know what deferred validation is and how it is used in the component lifecycle.
4. True or False. I know when the preinitialize, initialize, creationComplete, and updateComplete are dispatched relative to createChildren(), commitProperties(), measure(), and updateDisplayList().

If you answered false to any of the above questions, I will cover the answers next. So keep reading :) .

Deferred Validation

Flex utilizes a Deferred Validation approach for rendering components in their various states. This is the central concept in the component lifecycle. In this approach, private global variables and boolean flags are used to defer render-related processing until the proper validation method is reached. The reason for this is that if the display of a component were redrawn every single time we changed a property our app would run way too slow!  Deferred validation allows component properties to be set one right after the other and then Flex queues the redraw of the component during the next render event.

There are four lifecycle functions that utilize deferred validation in Flex that can be overridden and used for processing opportunities before rendering, they are: createChildren(), commitProperties(), measure(), and updateDisplayList(). Three of these four (excluding createChildren()) also have validation trigger methods that allow you to schedule one of the lifecycle functions to be executed during the next render event. They will be discussed later on, but for illustration they are included below:

invalidateProperties() –> commitProperties()‏
invalidateSize() –> measure()‏
invalidateDisplayList() –> updateDisplayList()‏

A broad overview of the component lifecycle is as follows...

  1. 1. The constructor is called and initial properties are set.
  2. 2. The preinitialize event is dispatched.
  3. 3. The createChildren() function is called.
  4. 4. The initialize event is dispatched.
  5. 5. The commitProperties() function is called.
  6. 6. The measure() function is called (if necessary).
  7. 7. The layoutChrome() function is called (very rare and will not be covered in this post).
  8. 8. The updateDisplayList() function is called.
  9. 9. The creationComplete event is dispatched.
  10. 10. The updateComplete event is dispatched.

Constructor and Initial Properties

Each custom component has a constructor whether it is explicitly defined or not. One benefit of ActionScript over other stricter languages like Java is that if you don't define a constructor, Flex will do it for you automatically when the application is built. The primary purpose of the constructor is to create a new instance of an object and to set initial properties for the object. An example of configuring the component's initial properties in the constructor follows:

public function Button():void{
  super();
  addEventListener(MouseEvent.CLICK, onClick);
  label = "Default Label";
}
 

Of course, we can also choose to set initial properties outside of the constructor before the component is added to the display list as the code below illustrates:

var button:Button = new Button();
button.label = "My Button";
 

preinitialize

The event is immediately dispatched by Flex when a custom component is added to the display list by either one of the two methods:

parent.addChild(customComponent);
 
<mx:Canvas>
  <lib:MyCustomComponent />
</mx:Canvas>
 

This event should be used sparingly as there are few things you can do with a component early in its life because it is in such a "raw" state. One possible use might be to set properties on the parent.

createChildren()

What is it's purpose?
The purpose of this function is to add other child components that the custom component may be comprised of. For example, a Button component consists of a Label sub-component which would be added to the Button (ex. addChild(myLabel)) in createChildren().

When is it called?
The createChildren() method is the first lifecycle function that is called by Flex upon adding the component to the display list. Unlike the other lifecycle functions, this method is called only once upon creation and is not able to be invalidated.

Example

private var scoreText:Text;
private var trophyImage:Image;

override protected function createChildren():void {
  // Call the createChildren() method of the superclass. You should use
  // this in most every case.super.createChildren();
  // Test for the existence of the children before creating them.
  if (!scoreText){
    scoreText = new Text();
    scoreText.setStyle("textAlign", "right");

    // Add the child component to the custom component.addChild(scoreText);
  }

  // Test for the existence of the children before creating them.
  if (!trophyImage){
    // For now we will not give the image a source because a score has
    // not been passed yet, so we don't know whether to display a gold
    // silver, or bronze trophy.trophyImage = new Image();
    // Add the child component to the custom component.addChild(clearBtn);
  }
}
 

initialize

This event is dispatched following the execution of createChildren(). At this point, the child components of our custom component have been added but not yet sized and layed out. You can utilize this event to perform additional processing before the sizing and layout procedures occur.

commitProperties()

What is it's purpose?

The purpose of this function is to commit any changes to custom properties and to synchronize changes so they occur at the same time or in a specific order.

When is it called?

The commitProperties() method is automatically called by Flex whenever the custom component is added to a parent by the call addChild(customComponent). It can also be scheduled to execute during the next render event by calling the invalidateProperties() method. When a render event occurs, commitProperties() will always be called before measure(), or updateDisplayList() if it is scheduled to execute.

Caveats to remember

  1. 1. There is a strong tendency for most developers to perform tasks in a setter as opposed to the commitProperties() function. Avoid this tendency at all costs! Setters should be as lightweight as possible and should use almost no processing except to schedule commitProperties() to execute. During commitProperties(), all of the processing you would have done in the setter is now done during commitProperties(). This can have a significant impact on performance because no matter how many times invalidateProperties() is called between render events, commitProperties() will only be executed once. This can really save on processing time and speed.
  2. 2. Use boolean flags as much as possible to indicate when a property has been changed in the setter and requires further processing in commitProperties()

Example

private var _data:Object;
// This is just a boolean flag that lets me know when the property has
// changed.private var _dataChanged:Boolean = false;
// Bindings are triggered whenever a "dataChange" event is dispatched
// within the component
[Bindable("dataChange")]
public function get data():Object{
  return _data;
}
public function set data(value:Object):void{
  _data = value;
  _dataChanged = true;

  // Schedule a commitProperties() call during the next rendering event
  invalidateProperties();

  dispatchEvent(new Event("dataChange"));
}

override protected function commitProperties():void {
  super.commitProperties();
 
  // When _dataChanged = true, we execute the condition. This boolean
  // flag implementation makes the component leaner because only the
  // properties that are changed are altered, instead of processing
  // all the properties even though some of them may not have changed
  if (_data &amp;&amp; _dataChanged){
    // Do the processing affected by the data change
    // (ex. change color, size, add event listeners, etc.)
    setScoreInfo(_data);_dataChanged = false;
  }
}
 

measure()

What is it's purpose?

The purpose of this function is to set a default width and height for your custom component. Additionally, you can also set a default minimum width and minimum height on your component.

When is it called?

Like the commitProperties() function, this function can be called many times. It can also be scheduled to execute during the next render event by calling the invalidateSize() method. However, Flex ensures that if it is scheduled, it will always be called after commitProperties().

Caveats to remember

  1. 1. measure() is only called if no explicit width or height have been given to the component.
  2. 2. Only four optional properties should be set in this function: measuredHeight, measuredWidth, measuredMinHeight, and measuredMinWidth
  3. 3. measure() is first called at the bottom of the display tree and works it way up to the top. This means that if you want your component's height and width to reflect the height and width of its children, you are free to do it here because your component's children have had their measure() functions called which means their height and width properties have already been set.
  4. 4. Usually to determine the width and height or children components, you use the getExplicitOrMeasuredHeight() or getExplicitOrMeasuredWidth() functions on the children components. These functions will return either the explicit size or the measured size of the child component.

Example

The example below sets the default width and height of a custom component to that of it's child (which is a Label component).

private static const DEFAULT_MIN_WIDTH:Number = 50;
private static const DEFAULT_MIN_HEIGHT:Number = 25;

override protected function measure():void {
  super.measure();
  // Make the default width and height of the component equal to the
  // height and width of the text label that it contains. We do this
  // instead of getExplicitOrMeasureWidth() and
  // getExplicitOrMeasuredHeight() due to the nature of the Label
  // component.
  var lineMetrics:TextLineMetrics = measureText(text);
 
  // Add a 10 pixel padding to all sides of the component
  measuredWidth = lineMetrics.width + 10;
  measuredHeight = lineMetrics.height + 10;
  measuredMinWidth = DEFAULT_MIN_WIDTH;
  measuredMinHeight = DEFAULT_MIN_HEIGHT;
}
 

updateDisplayList(unscaledWidth:Number, unscaledHeight:Number)

What is it's purpose?

The purpose of this function is to set the size and position for all child components that comprise the custom component. This function is also used place borders, skins, styles, and to draw on the custom component using ActionScript's Drawing API.

When is it called?

Like the commitProperties() and measure() functions, this function can be called many times. It can also be scheduled to execute during the next render event by calling the invalidateDisplayList() method. However, Flex ensures that if it is scheduled, it will always be called after measure() (if measure was already scheduled that is).

Caveats to remember

  1. 1. Avoid the temptation to set the size and position of child components by doing:
  2. child.width = 100;
    child.height = 120;
    child.x = 20;
    child.y = 20;
     

    Instead, there are two methods you should call: setActualSize() and move(). The following two methods should only be called in updateDisplayList() and nowhere else.

    child.setActualSize(100, 120);
    child.move(20, 20);
     
  3. The unscaledHeight and unscaledWidth arguments represent the new width of the custom component before scaling is applied. If you want to scale your component, you need to change its width and height to match the scale ratios.
  4. updateDisplayList() is first called at the top of the display tree and works it way down to the bottom. The unscaledHeight and unscaledWidth are given to the custom component based on the size that it's parent container allocated it

Example

private const LABEL_WIDTH:Number = 30;
private const HORIZONTAL_GAP:Number = 3;
private const IMAGE_WIDTH:Number = 16;
private const COMPONENT_HEIGHT:Number = 16;

override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void{
  super.updateDisplayList(unscaledWidth, unscaledHeight);
  if (score){
    var horizontalCenter:Number = unscaledWidth / 2;
    var innerCenter:Number = (LABEL_WIDTH + HORIZONTAL_GAP +IMAGE_WIDTH)/2;
   
    label.move(horizontalCenter - innerCenter, 0);
    label.setActualSize(LABEL_WIDTH, COMPONENT_HEIGHT);
   
    image.move((horizontalCenter - innerCenter + LABEL_WIDTH +HORIZONTAL_GAP), 0);
    image.setActualSize(IMAGE_WIDTH, COMPONENT_HEIGHT);
  }
}
 

creationComplete

This event is dispatched only once in the component lifecycle after createChildren(), commitProperties(), measure(), and updateDisplayList() are called one time each. When this event is dispatched, the custom component will have been configured, sized, and positioned in the display space.

updateComplete

This event is dispatched every time the updateDisplayList() function is executed. This gives you one final chance to change the component's layout or position before it is altered on the screen until the next render event.

Custom Component Lifecycle presentation given by Nate Ross at Rain

By: Nathan Ross Categories: flex Tags: , ,

Comments are closed.