AIR Mobile :: Hello BusyIndicator take 2
This article is Take 2 on the last article discussing the BusyIndicator.
"A good component developer breaks things into pieces, smashing them on the floor, putting the parts back into classes"
See Air Mobile :: Hello BusyIndicator take 1
What Needs To Be Fixed
- Ditch the
BusyPopUpSkinand use a special curcumstancepartAdded()implementation for adding skinparts. - Move popup layout logic to custom component class.
- Add ability to notify user of updates through a
statusinterface on the component.
The new component;

Note: These articles you read of mine are going to show you the things that a lot of tutorials don’t. I have years of experience making components and know one thing, do what your heart tells you to do, not what the book says. You need experience, but once you have it, walk your own path and make it work the way it should not how it is supposed to work.
Let’s start off by looking at the BusyLabelPopUp custom component’s code; (yes read it before continuing)
package components { import flash.geom.Point; import spark.components.BusyIndicator; import spark.components.Label; import spark.components.SkinnablePopUpContainer; import spark.layouts.HorizontalAlign; import spark.layouts.VerticalAlign; import spark.layouts.VerticalLayout; /** * A PopUp that contains a BusyIndicator and Label for status updates. * * @author Michael Schmalle * @copyright Teoti Graphix, LLC * @productversion 1.0 */ public class BusyLabelPopUp extends SkinnablePopUpContainer { //-------------------------------------------------------------------------- // // SkinPart :: Variables // //-------------------------------------------------------------------------- //---------------------------------- // busyIndicator //---------------------------------- [SkinPart(required="false")] /** * The busy indicator that notifies the user of indeterminate progress. */ public var busyIndicator:BusyIndicator; //---------------------------------- // statusDisplay //---------------------------------- [SkinPart(required="false")] /** * The Label that will display the status message if any. */ public var statusDisplay:Label; //-------------------------------------------------------------------------- // // Public :: Properties // //-------------------------------------------------------------------------- //---------------------------------- // status //---------------------------------- /** * @private */ private var statusChanged:Boolean = false; /** * @private */ private var _status:String; /** * Sets the status message on the popup. */ public function get status():String { return _status; } /** * @private */ public function set status(value:String):void { if (_status == value) return; _status = value; statusChanged = true; invalidateProperties(); } //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. */ public function BusyLabelPopUp() { super(); } //-------------------------------------------------------------------------- // // Overridden Public :: Methods // //-------------------------------------------------------------------------- override public function updatePopUpPosition():void { // use the owner x, y coords var point:Point = new Point(owner.x, owner.y); // convert the owner x,y to global so this can be laid out in // nested Views point = owner.parent.localToGlobal(point); // set the popup's global x,y coords x = point.x; y = point.y; // mirror the owner's width and height onto the popup width = owner.width; height = owner.height; } //-------------------------------------------------------------------------- // // Overridden Protected :: Methods // //-------------------------------------------------------------------------- override protected function partAdded(partName:String, instance:Object):void { super.partAdded(partName, instance); if (instance == contentGroup) { // if the contentGroup has 0 children right now, we know // a custom skin was not applied and we should create the // skinparts programatically // this is being 'polite' to future devs that may need an // MXML skin implementation adding skinparts declarativly if (contentGroup.numElements == 0) { // update layout knowing we are adding composite skinparts updateLayout(); // create the busyIndicator skinpart createBusyIndicator(); // create the statusDisplay skinpart createStatusDisplay(); } } } override protected function commitProperties():void { super.commitProperties(); if (statusChanged) { // this is optional but smart, create a commit method so your // devs that follow have a hook to change your code down the road commitStatus(); statusChanged = false; } } //-------------------------------------------------------------------------- // // Protected :: Methods // //-------------------------------------------------------------------------- protected function updateLayout():void { // create a nice and simple VerticalLayout that centers both parts // in the full layout. Remeber that the skin sets all layout // constraints to 0, effectively saying width=100%, height=100% var vlayout:VerticalLayout = new VerticalLayout(); vlayout.horizontalAlign = HorizontalAlign.CENTER; vlayout.verticalAlign = VerticalAlign.MIDDLE; vlayout.gap = 10; layout = vlayout; } protected function createBusyIndicator():void { // this could also be a factory, for simplicity we create here // this is really no different than what the mobile skins do busyIndicator = new BusyIndicator(); // setting the id allows CSS #busyIndicator busyIndicator.id = "busyIndicator"; contentGroup.addElement(busyIndicator); } protected function createStatusDisplay():void { // this could also be a factory, for simplicity we create here // this is really no different than what the mobile skins do statusDisplay = new Label(); // setting the id allows CSS #statusDisplay statusDisplay.id = "statusDisplay"; contentGroup.addElement(statusDisplay); } protected function commitStatus():void { if (!statusDisplay) return; // update the status text to show on the user interface statusDisplay.text = status; } } }
Was All This Code Worth It
Yes, you are looking at a good abstraction of a user interface component that displays a BusyIndicator and Label. This class could be reused, subclassed and skinned. This is the ultimate sunny day for an application developer trust me. Most of the time I see good developers just not wanting to take the time to write good solid component code. This is also where we will get into the eternal fight of unit testing verses refactoring. I guess it comes down to whoever wins the race right? I am confident I will win with the way I write my components, are you confident in your user interface component code (oh you don’t have any reusable components)?
Creating the API
One of the first things I do is break down what is the external (public) communication to this component. Basically we have one thing, the status message. I mentioned in the previous article that the BusyIndicator is basically private meaning locked doors, there is nothing to expose with it. The styles are already there to access with CSS.
Creating the User Interface blocks
The blocks of a component in the Spark framework are skinparts. We know we needed a BusyIndicator, that is busyIndicator skin part. We also know we need a way to express the status text. For simplicity we use the Label that can display single and multiline text. For that we create the statusDisplay skin part.
SkinPart Addition
Here is the controversial part of this article. I am not creating a skin for this component. We actually create the skin parts in the BusyLabelPopUp.
- We honor the spark skin contract by checking the
numElementsof thecontentGroupso we don’t add composites if they are already there. - We create a new vertical
layoutthat will size correctly and center thebusyIndicatorandstatusDisplay.- Remember the
layoutof theSkinnablePopUpContaineris proxied to thecontentGroup.
- Remember the
- We create two hook methods for the creation of the
busyIndicatorandstatusDisplay.- This allows for subclass overrides, remember
protectedin a well designed component is good karma.
- This allows for subclass overrides, remember
- When creating skinparts in ActionScript, remember to set the
id, if you don’t you cannot style them from id selectors in CSS.
Property Commital
Understanding the invalidation passes of a UIComponent is very important. If you get this right, Flex components are very fast. A lot of the times developers curse the Flex framework for it’s bottlenecks in rendering. The sad truth is about 80% of the time it’s those very developers that are creating the bottlenecks with their code.
commitProperties()
The standard property change algorithm is;
- Check to see if the property has changed.
- Set a backing variable usually
private. - Switch a flag that will be picked up in
commitProperties() - Call
invalidateProperties()to triggercommitProperties()on the next frame update. - If the property is
Bindable, calldispatchEvent(new Event("propertyChanged"))
Once the status property has changed, a frame will pass and commitProperties() will fire. During this method call, if the statusChanged flag is true, commitStatus() will be called and the statusDisplay.text will be updated with the new text.
Note: There is also a pattern where the statusDisplay.text gets called directly in the setter. This has to do with the sync between parent and child. In this example it really dosn’t matter and the commit hook method allows for future adjustment very easily.
PopUp Position And Layout
The layout and position logice we taken out of the view and the SkinnablePopUpContainer has a nice public method that can AND should be overridden; updatePopUpPosition().
In this method we convert the owner‘s point, in the case of the example it’s a View into gloabl coords and apply those x and y values to the popup.
The Updated View Code
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Take2"> <fx:Script> <![CDATA[ import components.BusyLabelPopUp; private var mTimer:Timer; private var mPopUp:BusyLabelPopUp; private var mCount:int = 0; protected function button1_clickHandler(event:MouseEvent):void { // if busy, return if (mPopUp) return; mCount = 0; createPopUp(); startOperation(); updateStatus("Loading..."); } private function createPopUp():void { mPopUp = new BusyLabelPopUp(); mPopUp.open(this); } //-------------------------------------------------------------------------- // // Bellow is just Application logic now // //-------------------------------------------------------------------------- private function startOperation():void { mTimer = new Timer(1000); mTimer.addEventListener(TimerEvent.TIMER, timerHandler); mTimer.start(); } private function endOperation():void { mPopUp.close(); mPopUp = null; } private function updateStatus(message:String):void { mPopUp.status = message; } private function timerHandler(event:TimerEvent):void { updateStatus("Loading... " + mCount++); if (mCount == 4) { mTimer.removeEventListener(TimerEvent.TIMER, timerHandler); mTimer = null; endOperation(); } } ]]> </fx:Script> <s:layout> <s:VerticalLayout horizontalAlign="center" paddingTop="50"/> </s:layout> <s:actionContent> <s:Button id="initiateButton" label="Set Busy" click="button1_clickHandler(event)"/> </s:actionContent> <!-- This button will not be touchable when the indicator is present --> <s:Button label="Can't touch this! :)"/> </s:View>
In the code above;
- Removed all popup logic and layout.
- Create the
BusyLabelPopUpand callopen(). - Create a fake operation that loops 4 times to demonstrate the dynamic status update of the busy overlay.
- There are only a couple lines of code in the above file that actually have to do with the new component, the rest is application logic.
Styling The PopUp
Now that the PopUp is in a custom component we have gained the ability to control the styles from the outside of ALL skin parts. Here is the application styling that targets our new skinparts of the BusyLabelPopUp component.
@namespace s "library://ns.adobe.com/flex/spark"; @namespace components "components.*"; components|BusyLabelPopUp { backgroundAlpha:0.3; backgroundColor:#000000; } components|BusyLabelPopUp #busyIndicator { symbolColor:#FFFFFF; rotationInterval:10; } components|BusyLabelPopUp #statusDisplay { fontWeight:bold; color:#FFFFFF; }
Conclusion
This Take 2 was really a tagent article I took talking about component development. It provided a good example of the two challenges that face application development; Speed verses Reusablity.
If you are developing mobile applications, think about components and what they can do for you. Along that note Teoti Graphix, LLC has a new mobile toolkit coming out and 3 more in the making;
Check out;
About the Author
Michael Schmalle has been developing Adobe Flex and the Flash Player since Flash version 5 (2002). He has numerous open source projects in ActionScript3 (including an ActionScript3 parser/lexer DOM for reading and writing AS3 source code) and Flex (user interface components). He has also been an active participant in the Flex community (just goolge him or Teoti Graphix, LLC) since 2004. He also realizes technologies change and is actively developing user interface components and applications on the Google Android mobile platform.
See Teoti Graphix. LLC.
GIT: https://github.com/teotigraphix
Twitter: http://twitter.com/TeotiGraphix/










Recent Comments