adobe flex component lifecycle
DESCRIPTION
A presentation given about the Adobe Flex component lifecycle by Brad Umbaugh and RJ Owen at 360 Flex in summer 2008, San Jose.TRANSCRIPT
Diving Deep with the Flex Component Lifecycle
Brad Umbaugh and RJ Owen
Who are we?
‣Brad Umbaugh• Senior Dev @ EffectiveUI, did globe on
Discovery Earth Live, the sort of guy you bring home to Mom.
‣RJ Owen• Senior Dev @ EffectiveUI, Adobe
Community Expert, Earth Live, done lots of projects, etc. etc. blah blah blah.
Who are you (hopefully)?
‣Beginner to intermediate level developers‣Anyone who doesn’t currently understand
the lifecycle‣Anyone who wants a good review of the
basics
What’s this about, anyway?
‣Flex component lifecycle (duh)‣Flex frame cycle (“elastic racetrack”)‣Brad’s inability to grow a real beard
Flex
‣What is Flex?• A set of components• MXML• The component lifecycle!
Flex Component Lifecycle
‣What is it?• The way the framework interacts with
every Flex component• A set of methods the framework calls to
instantiate, control, and destroy components
• Methods that make the most of the elastic racetrack
Elastic Racetrack: introduction
‣Flex component lifecycle is built on this frame model‣More on this later
image courtesy of Ted Patrick
A frame in AS3
image courtesy of Sean Christmann
Phases of the Lifecycle
‣3 Main Phases:‣ BIRTH:
• construction, configuration, attachment, initialization
‣ LIFE: • invalidation, validation, interaction
‣DEATH: • detachment, garbage collection
BirthCongratulations: You’re about to have a component.
Construction
Birthconstructionconfigurationattachmentinitialization
LifeDeath
What is a constructor?
‣A function called to instantiate (create in memory) a new instance of a class
Birthconstructionconfigurationattachmentinitialization
LifeDeath
How is a constructor invoked?
Actionscript:
<mx:Label id="theLabel"/>MXML:
var theLabel : Label = new Label();
Birthconstructionconfigurationattachmentinitialization
LifeDeath
What does a constructor have access to?
‣Properties on the class‣Methods on the class‣Children have not yet been created!
Birthconstructionconfigurationattachmentinitialization
LifeDeath
What does an ActionScript3 constructor look like?
‣ No required arguments (if it will be used in MXML); zero, or all optional
‣ Only one per class (no overloading!)‣ No return type‣ Must be public‣ Calls super() to invoke superclass constructor; if
you don’t, the compiler will!
public function ComponentName(){super();//blah blah blah
}
Birthconstructionconfigurationattachmentinitialization
LifeDeath
What does an MXML constructor look like?
‣No need to define one. In fact, if you try to put one in an <mx:Script> block, you’ll get an error. ‣Why? Remember: MXML = Actionscript. A
constructor is created by the compiler in the Actionscript generated from the MXML.‣Specify “-keep” in the Flex Builder
compiler arguments and look at the generated code to verify this.
Birthconstructionconfigurationattachmentinitialization
LifeDeath
What should a constructor do?
‣Not much. Since the component’s children have not yet been created, there’s not much that can be done.‣There are specific methods (such as
createChildren) that should be used for most of the things you’d be tempted to put in a constructor.‣A good place to add event listeners to the
object.Birth
constructionconfigurationattachmentinitialization
LifeDeath
Don’t create or attach children in the constructor
‣ It’s best to delay the cost of createChildren calls for added children until it’s necessary
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Configuration
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Configuration
‣The process of assigning values to properties on objects‣ In MXML, properties are assigned in this
phase, before components are attached or initialized<local:SampleChild property1="value!"/>
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Hooray: Sample code!
SampleChild constructorSampleChild.property1 setterAdding child SampleChild4
<mx:Application ...> ...<local:SampleChild property1="value!"/>
</mx:Application>
Output:
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Configuration and Containers
‣Containers must not expect their children have to be instantiated when properties are set.<mx:Application ...> <local:SampleContainer property1="value!"> <local:SampleChild property1="value!"/> </local:SampleContainer></mx:Application>
SampleContainer constructorSampleContainer.property1 setterSampleChild constructorSampleChild.property1 setter
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Configuration Optimization
‣To avoid performance bottlenecks, make your setters fast and defer any real work until validation‣We’ll talk more about deferment in the
validation / invalidation section
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Attachment
Birthconstructionconfigurationattachmentinitialization
LifeDeath
What is attachment?
‣Adding a component to the display list (addChild, addChildAt, MXML declaration)
‣The component lifecycle is stalled after configuration until attachment occurs.
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Consider this component: public class A extends UIComponent { public function A() { trace( "CONSTRUCTOR" ); super(); } override protected function createChildren() : void { trace( "CREATECHILDREN" ); super.createChildren(); } override protected function measure() : void { trace( "MEASURE" ); super.measure(); } override protected function updateDisplayList(width:Number, height:Number) : void { trace( "UPDATEDISPLAYLIST" ); super.updateDisplayList(width,height); } override protected function commitProperties():void { trace( "COMMITPROPERTIES" ); super.commitProperties(); }
(It traces all of its methods.)
And this application:
‣Without attachment, the rest of the lifecycle doesn’t happen.
<mx:Application ...> <mx:Script> <![CDATA[ override protected function createChildren() : void { super.createChildren(); var a : A = new A(); } ]]> </mx:Script></mx:Application>
CONSTRUCTOROutput:
But what about this application?
‣Moral of the story: don’t add components to the stage until you need them.
<mx:Application ...> <mx:Script> <![CDATA[ override protected function createChildren() : void { super.createChildren(); var a : A = new A();
this.addChild( a ); } ]]> </mx:Script></mx:Application> Output: CONSTRUCTOR
CREATECHILDRENCOMMITPROPERTIESMEASUREUPDATEDISPLAYLIST
Initialization
Birthconstructionconfigurationattachmentinitialization
LifeDeath
Initialization
‣2 phases, 3 events:
1. ‘preInitialize’ dispatched2. createChildren(); called3. ‘initialize’ dispatched4. first validation pass occurs5. ‘creationComplete’ dispatched
Create
Validate
Birthconstructionconfigurationattachmentinitialization
LifeDeath
createChildren()‣ MXML uses the createChildren() method to add
children to containers‣ Override this method to add children using AS
• Follow MXML’s creation strategy: create, configure, attach
override protected function createChildren():void{
...textField = new UITextField();
textField.enabled = enabled;textField.ignorePadding = true;textField.addEventListener("textFieldStyleChange", textField_textFieldStyleChangeHandler);
... ... addChild(DisplayObject(textField));}
create
configure
attach
createChildren() cont.
‣Defer creating dynamic and data-driven components until commitProperties()
‣UIComponent.createChildren() is empty, but it’s good practice to always call super.createChildren() anyway
Birthconstructionconfigurationattachmentinitialization
LifeDeath
first validation pass
‣ Invalidation is not part of initialization - only Validation‣Validation consists of 3 methods:
• commitProperties()• measure()• updateDisplayList()
‣more on these laterBirth
constructionconfigurationattachmentinitialization
LifeDeath
LifeThey grow up so fast.
Invalidation
BirthLife
invalidationvalidationinteraction
Death
Invalidation / Validation cycle
‣ Flex imposes deferred validation on the Flash API• goal: defer screen updates until all
properties have been set‣ 3 main method pairs to be aware of:
• invalidateProperties() -> commitProperties()
• invalidateSize() -> measure()• invalidateDisplayList() ->
updateDisplayList()
Invalidation / Validation theory
‣First, a little theory.
Deferment
‣Deferment is the central concept to understand in the component Life-cycle
‣Use private variables and boolean flags to defer setting any render-related properties until the proper validation method
Text-book example
public function set text(value:String):void{ myLabel.text = value;// Possible Error! during first config phase, // myLabel might not exist!}
private var _text:String = "";public function set text(value:String):void{ textSet = true; _text = value; textChanged = true;
invalidateProperties(); invalidateSize(); invalidateDisplayList();}
override protected function commitProperties():void{{
if(textChanged){myLabel.text = text;textChanged = false;
}super.commitProperties();
}
Bad:
Good:
The Elastic Racetrack revisited
Invalidation occurs here
image courtesy of Sean Christmann
Invalidation methods
‣ invalidateProperties()• Any property changes
‣ invalidateSize()• Changes to width or height
‣ invalidateDisplayList()• Changes to child component size or
positionBirthLife
invalidationvalidationinteraction
Death
Invalidation example 1<mx:Application> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; [Bindable] public var arr : ArrayCollection = new ArrayCollection(); public function onClick() : void { var c : int = 0; while( c++ < 20 ) { arr.addItem( c ); } } ]]> </mx:Script> <mx:VBox> <mx:Button label="Click me!" click="onClick()"/> <test:BadList id="theList" dataProvider="{arr}"/> </mx:VBox></mx:Application>
Invalidation example 2public class BadList extends VBox
{ private var _dataProvider : ArrayCollection; public function set dataProvider( arr : ArrayCollection ) : void { this._dataProvider = arr; arr.addEventListener( CollectionEvent.COLLECTION_CHANGE, dataProviderChangeHandler ); } private function dataProviderChangeHandler( e : Event ) : void { this.removeAllChildren(); for each( var n : Number in this._dataProvider ) { var l : Label = new Label(); l.text = n.toString(); this.addChild( l );
} } public function BadList() {} }
Result: dataProviderChangeHandler called 20 times
Invalidation example 3public class GoodList extends VBox
{ private var _dataProvider : ArrayCollection; private var _dataProviderChanged : Boolean = false; public function set dataProvider( arr : ArrayCollection ) : void { this._dataProvider = arr; arr.addEventListener( CollectionEvent.COLLECTION_CHANGE, dataProviderChangeHandler ); this._dataProviderChanged = true; this.invalidateProperties(); } override protected function commitProperties():void { super.commitProperties(); if( this._dataProviderChanged ) { this.removeAllChildren(); for each( var n : Number in this._dataProvider ) { var l : Label = new Label(); l.text = n.toString(); this.addChild( l ); } this._dataProviderChanged = false; } } private function dataProviderChangeHandler( e : Event ) : void { this._dataProviderChanged = true; this.invalidateProperties(); } public function GoodList() {} }
Result: commitProperties called only twice (once during initialization)
Validation
BirthLife
invalidationvalidationinteraction
Death
The Elastic Racetrack revisited
Validation occurs here
Validation
‣Apply the changes deferred during invalidation‣Update all visual aspects of the
application in preparation for the render phase‣3 methods:
• commitProperties()• measure()• updateDisplayList()
BirthLife
invalidationvalidationinteraction
Death
commitProperties()
‣Ely says: “Calculate and commit the effects of changes to properties and underlying data.”‣ Invoked first - immediately before
measurement and layout
BirthLife
invalidationvalidationinteraction
Death
commitProperties() cont.
‣ALL changes based on property and data events go here‣Even creating and destroying children, so
long as they’re based on changes to properties or underlying data‣Example: any list based component with
empty renderers on the screenBirthLife
invalidationvalidationinteraction
Death
measure()
‣Component calculates its preferred (“default”) and minimum proportions based on content, layout rules, constraints.‣Measure is called bottom up - lowest
children first‣Caused by “invalidateSize()”‣NEVER called for explicitly sized
componentsBirthLife
invalidationvalidationinteraction
Death
overriding measure()
‣Used for dynamic layout containers (VBox, etc.)‣Use getExplicitOrMeasuredWidth() (or
height) to get child proportions‣ALWAYS called during initialization‣Call super.measure() first!‣Set measuredHeight, measuredWidth for
the default values; measuredMinHeight and measuredMinWidth for the minimum.
BirthLife
invalidationvalidationinteraction
Death
measure() cont.
‣Not reliable - Framework optimizes away any calls to measure it deems “unecessary”‣Ely says: “Start by explicitly sizing your
component and implement this later.”
BirthLife
invalidationvalidationinteraction
Death
updateDisplayList()
‣All drawing and layout code goes here, making this the core method for all container objects‣Caused by invalidateDisplayList();‣Concerned with repositioning and
resizing children‣updateDisplayList() is called top-down
BirthLife
invalidationvalidationinteraction
Death
Overriding updateDisplayList()
‣Usually call super.updateDisplayList() first• super() is optional - don’t call it if you’re
overriding everything it does‣Size and lay out children using move(x,y)
and setActualSize(w,h) if possible• I never have good luck with
setActualSize()BirthLife
invalidationvalidationinteraction
Death
Elastic Racetrack cont.
‣User Actions• Dispatch invalidation events• Interact with any non-validation events
from this frame (mouse movements, timers, etc.)
Elastic Racetrack Cont.
‣ Invalidate Action• Process all validation calls
‣Render Action• Do the heavy lifting - actually draw on
the screen
The Elastic Racetrack revisited
Queued InvalidationDeferred Validation
Render!
Interaction
BirthLife
invalidationvalidationinteraction
Death
How do objects know when something happens?
‣Events: objects passed around when anything interesting goes on (clicks, moves, changes, timers...)‣ If something happens to a component, it
“fires” or “dispatches” the event‣ If another component wants to know
when something happens, it “listens” for events‣Event-based architecture is loosely-
coupled
BirthLife
invalidationvalidationinteraction
Death
Benefits of Loosely-Coupled Architectures
‣Everything becomes more reusable‣Components don’t have to know anything
about the components in which they’re used
BirthLife
invalidationvalidationinteraction
Death
Who can dispatch events?
‣Subclasses of EventDispatcher• EventDispatcher inherits directly from
Object‣Simply call dispatchEvent(event) to fire off
an event when something happens
BirthLife
invalidationvalidationinteraction
Death
How to tell events apart?
‣Event class• Different classes allow for customized
payloads‣ “type” field: a constant
BirthLife
invalidationvalidationinteraction
Death
Common Events
‣Event.CHANGE‣MouseEvent.CLICK‣FlexEvent.CREATION_COMPLETE‣Event.RESIZE‣MouseEvent.ROLL_OUT
BirthLife
invalidationvalidationinteraction
Death
Handling Events
‣<mx:Button id=”theButton” click=”callThisFunction(event)”/>‣ theButton.addEventListener( MouseEvent
.CLICK, callThisFunction )
BirthLife
invalidationvalidationinteraction
Death
Event Propagation
Capturing Phase
Target
Application Application
Bubbling Phase
Targeting Phase
‣ Three phases: Capturing, Targeting, Bubbling
BirthLife
invalidationvalidationinteraction
Death
Event Propagation‣ Three phases: Capturing, Targeting, Bubbling
<mx:Application initialize="onInitialize()"><mx:Script> <![CDATA[ public function onInitialize() : void { this.addEventListener( MouseEvent.CLICK, clickHandler, true ); this.addEventListener( MouseEvent.CLICK, clickHandler, false ); outer.addEventListener( MouseEvent.CLICK, clickHandler, true ); outer.addEventListener( MouseEvent.CLICK, clickHandler, false ); inner.addEventListener( MouseEvent.CLICK, clickHandler, true ); inner.addEventListener( MouseEvent.CLICK, clickHandler, false ); button.addEventListener( MouseEvent.CLICK, clickHandler, true ); button.addEventListener( MouseEvent.CLICK, clickHandler, false ); } public function clickHandler( e : Event ) : void { trace("----------------------------------------------------------"); trace("TARGET: " + e.target.id ); trace("CURRENT TARGET: " + e.currentTarget.id ); trace("PHASE: " + ( e.eventPhase == 1 ? "CAPTURE" : ( e.eventPhase == 2 ? "TARGET" : "BUBBLE" ) ) ); } ]]></mx:Script> <mx:VBox> <mx:Panel id="outer"> <mx:TitleWindow id="inner"> <mx:Button id="button"/> </mx:TitleWindow> </mx:Panel> </mx:VBox></mx:Application>
‣
BirthLife
invalidationvalidationinteraction
Death
Event Propagation----------------------------------------------------------TARGET: buttonCURRENT TARGET: eventTestPHASE: CAPTURE----------------------------------------------------------TARGET: buttonCURRENT TARGET: outerPHASE: CAPTURE----------------------------------------------------------TARGET: buttonCURRENT TARGET: innerPHASE: CAPTURE----------------------------------------------------------TARGET: buttonCURRENT TARGET: buttonPHASE: TARGET----------------------------------------------------------TARGET: buttonCURRENT TARGET: innerPHASE: BUBBLE----------------------------------------------------------TARGET: buttonCURRENT TARGET: outerPHASE: BUBBLE----------------------------------------------------------TARGET: buttonCURRENT TARGET: eventTestPHASE: BUBBLE
BirthLife
invalidationvalidationinteraction
Death
Stopping events from propagating
‣ stopPropagation() : Prevents processing of any event listeners in nodes subsequent to the current node in the event flow‣ stopImmediatePropagation() : Prevents
processing of any event listeners in the current node and any subsequent nodes in the event flow
BirthLife
invalidationvalidationinteraction
Death
target vs. currentTarget
‣ target: the object that dispatched the event (doesn’t change)‣ currentTarget: the object who is currently
being checked for specific event listeners (changes)
BirthLife
invalidationvalidationinteraction
Death
Dispatching events from custom components
‣MXML:
‣Actionscript:
<mx:Metadata> [Event(name="atePizza", type="flash.events.BradEvent")]</mx:Metadata>
[Event(name="atePizza", type="flash.events.BradEvent")]public class MyComponent extends UIComponent{ ...}
BirthLife
invalidationvalidationinteraction
Death
gutterShark: event manager
‣guttershark is Aaron Smith’s “Actionscript 3 Productivity Library”‣Contains a lot of stuff commonly needed
when developing Flash web sites‣ Includes an EventManager that’s helpful
BirthLife
invalidationvalidationinteraction
Death
gutterShark: event manager
BirthLife
invalidationvalidationinteraction
Death
<mx:Application initialize="onInitialize()"><mx:Script> <![CDATA[ import net.guttershark.events.EventManager; private var em : EventManager; public function onInitialize( e : Event = null ) : void { em = EventManager.gi(); em.handleEvents(theButton,this,"onButton"); } public function onButtonClick() : void { theLabel.text = "CLICK"; } public function onButtonMouseOver() : void { theLabel.text = "MOUSE OVER"; } public function onButtonMouseOut() : void { theLabel.text = "MOUSE OUT"; } ]]></mx:Script> <mx:VBox> <mx:Button id="theButton" label="Click Me!"/> <mx:Label id="theLabel"/> </mx:VBox></mx:Application>
gutterShark: event manager
‣Supports many different types of events and the addition of more‣Easy integration with Google Analytics,
Atlas, Webtrends‣http://www.guttershark.net
BirthLife
invalidationvalidationinteraction
Death
DeathAll good things come to an end.
Detachment
BirthLifeDeath
detachmentgarbage collection
Detachment
‣ “Detachment” refers to the process of removing a child from the display list‣These children can be re-parented
(brought back to life) or abandoned to die‣Abandoned components don’t get
validation calls and aren’t drawn‣ If an abandoned component has no more
active references, it *should* be garbage-collected
BirthLifeDeath
detachmentgarbage collection
Detachment cont.
‣Re-parenting isn’t cheap, but it’s cheaper than re-creating the same component twice‣Children do not need to be removed from
their parent before being re-parented, but always should be‣Consider hiding rather than removing
• set visible and includeInLayout to falseBirthLifeDeath
detachmentgarbage collection
Garbage Collection
BirthLifeDeath
detachmentgarbage collection
Garbage Collection
‣The process by which memory is returned to the system‣Only objects with no remaining references
to them will be gc’d• Set references to detached children to
“null” to mark them for GC‣Unreliable, like Brad‣Talk to Grant Skinner about forcing GC
• http://gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
BirthLifeDeath
detachmentgarbage collection
Conclusion
‣Defer, Defer, DEFER!‣Use validation methods correctly‣Remember the elastic racetrack‣Always look on the bright side of
detachment.
References‣ Ely Greenfield: “Building a Flex Component”• http://www.onflex.org/ACDS/
BuildingAFlexComponent.pdf
‣ Chafic Kazoun, Joey Lott: “Programming Flex 2” by O’Reilly• http://oreilly.com/catalog/9780596526894/
‣ Colin Moock: “Essential Actionscript 3.0” by O’Reilly• http://oreilly.com/catalog/9780596526948/
index.html
Questions?
Brad [email protected]: bradumbaughhttp://bradumbaugh.blogspot.com/
RJ [email protected]: rjowenhttp://rjria.blogspot.com
We’re hiring: jobs.effectiveui.com