Upload
rj-owen
View
36.742
Download
1
Tags:
Embed Size (px)
DESCRIPTION
An updated version of our Adobe Flex 3 Component Lifecycle talk. I gave this presentation at CFUnited Express in Denver on 4/6/2009. Brad Umbaugh and I gave the original in Nov. 2008 at 360 Flex San Jose. Brad Umbaugh wrote half of these slides.
Citation preview
Adobe Flex 3 Component Lifecycle
Presented by RJ Owen
This presentation originally created by RJ Owen and Brad Umbaugh in 11/08.
Who are we?
‣RJ Owen• Senior Technical Architect @ EffectiveUI• Adobe Community Expert in Flex
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”)‣Deferment!
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, con"guration, attachment, initialization
‣ LIFE: • invalidation, validation, interaction
‣DEATH: • detachment, garbage collection
BirthCongratulations: You’re about to have a component.
Construction
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
What is a constructor?
‣A function called to instantiate (create in memory) a new instance of a class
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
How is a constructor invoked?
Actionscript:
<mx:Label id="theLabel"/>MXML:
var theLabel : Label = new Label();
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
What does a constructor have access to?
‣Properties on the class‣Methods on the class‣Children have not yet been created!
Birthconstructioncon!gurationattachmentinitialization
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
}
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
What does an MXML constructor look like?
‣No need to de"ne 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.
Birthconstructioncon!gurationattachmentinitialization
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 speci"c 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
constructioncon!gurationattachmentinitialization
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
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Con"guration
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Con"guration
‣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!"/>
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Hooray: Sample code!
SampleChild constructorSampleChild.property1 setterAdding child SampleChild4
<mx:Application ...> ...<local:SampleChild property1="value!"/>
</mx:Application>
Output:
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Con"guration 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
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Con"guration 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
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Attachment
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
What is attachment?
‣Adding a component to the display list (addChild, addChildAt, MXML declaration)
‣The component lifecycle is stalled after con"guration until attachment occurs.
Birthconstructioncon!gurationattachmentinitialization
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
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
Initialization
‣2 phases, 3 events:
1. ‘preInitialize’ dispatched2. createChildren(); called3. ‘initialize’ dispatched4. "rst validation pass occurs5. ‘creationComplete’ dispatched
Create
Validate
Birthconstructioncon!gurationattachmentinitialization
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, con"gure, attach
override protected function createChildren():void{
...textField = new UITextField();
textField.enabled = enabled;textField.ignorePadding = true;textField.addEventListener("textFieldStyleChange", textField_textFieldStyleChangeHandler);
... ... addChild(DisplayObject(textField));}
create
con"gure
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
Birthconstructioncon!gurationattachmentinitialization
LifeDeath
"rst validation pass
‣ Invalidation is not part of initialization - only Validation‣Validation consists of 3 methods:
• commitProperties()• measure()• updateDisplayList()
‣more on these laterBirth
constructioncon!gurationattachmentinitialization
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 #ags 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 "rst - 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 "rst‣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() "rst!‣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() "rst• 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
“"res” 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
Bene"ts 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 "re off
an event when something happens
BirthLife
invalidationvalidationinteraction
Death
How to tell events apart?
‣Event class• Different classes allow for customized
payloads‣ “type” "eld: 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 #ow‣ stopImmediatePropagation() : Prevents
processing of any event listeners in the current node and any subsequent nodes in the event #ow
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 speci"c 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‣Consider using weak references on event
listeners‣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 Green"eld: “Building a Flex Component”• http://www.on#ex.org/ACDS/
BuildingAFlexComponent.pdf
‣ Cha"c 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?
RJ [email protected]: rjowenhttp://rjria.blogspot.com