Archive

Posts Tagged ‘busyindicator’

AIR Mobile :: Hello BusyIndicator take 2

December 5th, 2011 Michael Schmalle 2 comments

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 BusyPopUpSkin and use a special curcumstance partAdded() implementation for adding skinparts.
  • Move popup layout logic to custom component class.
  • Add ability to notify user of updates through a status interface 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 numElements of the contentGroup so we don’t add composites if they are already there.
  • We create a new vertical layout that will size correctly and center the busyIndicator and statusDisplay.
    • Remember the layout of the SkinnablePopUpContainer is proxied to the contentGroup.
  • We create two hook methods for the creation of the busyIndicator and statusDisplay.
    • This allows for subclass overrides, remember protected in a well designed component is good karma.
  • 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 trigger commitProperties() on the next frame update.
  • If the property is Bindable, call dispatchEvent(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 BusyLabelPopUp and call open().
  • 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/

Categories: AIR Mobile Tags: ,

Air Mobile :: Hello BusyIndicator take 1

December 5th, 2011 Michael Schmalle No comments

The BusyIndicator defines a component to display when a long-running operation is in progress.

What is the BusyIndicator

The BusyIndicator is a UIComponent that plays an animation while a background operation is running to alert the user of indeterminate progress. The BusyIndicator by default draws twelve spokes that set each spoke’s alpha in intervals of about 0.1 foreach rotation interval. This gives the illusion that the indicator is spinning.

Metrics

  • 160 DPI – 26×26 pixels
  • 240 DPI 40×40 pixels
  • 320 DPI 52×52 pixels

It’s important to note that the above dimensions are measured. If the width and height are increased or decreased, the component will fill the minimum of the two.

What Is The BusyIndicator Used For

As mentioned, the BusyIndicator is used to notify the user of a long running operation. This component due to the way it was programmed is not very "extendable". Most of the internal methods are private or mx_internal meaning as a responsible developer you cannot even override the draw logic without opening up the mx_internal namespace.

The BusyIndicator could be used in a modal PopUpContainer to disallow user interaction while the operation is in progress. The BusyIndicator could be placed within the ActionBar to notify the user in a less obtrusive nature. The BusyIndicator could just be placed on top of ViewNavigator View instances to act as a non interuptive progress targeting the View that is loading data.

With either of the options listed above it’s important to note that the BusyIndicator is just a plain old UIComponent under the covers. This means it’s a standard display object that can be placed where ever needed.

How to Sart And Stop The Rotation

Starting and stopping the rotation is out of the hands of the developer. The component listens for ADDED_TO_STAGE and REMOVED_FROM_STAGE events to start and stop it’s animation.

How To Style The BusyIndicator

The BusyIndicator has two styles;

  • rotationInterval:Number – In milliseconds that amount of relative spin given to the indicator.
  • symbolColor:uint – The color of the indicator’s spokes.

Getting Started with The With The BusyIndicator :: Take1

To start using the BusyIndicator, it’s as easy as placing the component in MXML or instantiating the component and adding it to the display list through ActionScript. Since the docs talk about that lets try some other strategies and see what we come up with.

Take 1 involves a newer developers take on the "touted" Spark skinning system and hey "just skin it!" attitude. The problem with skinning a component like the SkinnablePopUpContainer is the fact the default skin has 110 lines of MXML code in it that we copy.

Note: It is assumed with the plethora of mobile tutorials out there that you know how to create Flex Mobile projects and understand how to create custom components.

Step 1: Create a new mobile application

  • Create and setup a new Flex Mobile Project using the View-Based Application template: HelloBusyIndicatior
    • Name the view Take1

Step 2: Create a new SkinnablePopUpContainer skin

  • Create a new MXML Skin named BusyPopUpSkin
    • File > New > MXML Skin
  • Fill in the inputs as listed below

create mxml skin

  • With the BusyPopUpSkin.mxml open add the BusyIndicator component to the contentGroup
...
<s:Group id="contentGroup" left="0" right="0" top="0" bottom="0" minWidth="0" minHeight="0">
    <s:layout>
        <s:BasicLayout/>
    </s:layout>
	<s:BusyIndicator id="busyIndicator" verticalCenter="0" horizontalCenter="0"/>
</s:Group>
...

What we have done here is added the indicator to the content of the SkinnablePopUpContainer so when it is created, the BusyIndicator is automtaically running and centered within the popup.

Step 3: Create the code that will use the PopUp

  • Go back to the Take1.mxml View and replace the code with the following
<?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="Take1">
 
	<fx:Script>
 
	import skins.BusyPopUpSkin;
 
	import spark.components.SkinnablePopUpContainer;
 
	private var mTimer:Timer;
 
	private var mPopUp:SkinnablePopUpContainer;
 
	protected function button1_clickHandler(event:MouseEvent):void
	{
		// if busy, return
		if (mPopUp)
			return;
 
		createPopUp();
		startOperation();
	}
 
	private function createPopUp():void
	{
		// create the SkinnablePopUpContainer
		mPopUp = new SkinnablePopUpContainer();
		// set the styles
		mPopUp.setStyle("skinClass", BusyPopUpSkin);
		mPopUp.setStyle("backgroundColor", 0x000000);
		mPopUp.setStyle("backgroundAlpha", 0.3);
 
		layoutPopUp();
 
		// call PopUpManger to open and add
		mPopUp.open(this);
 
		positionPopUp();
	}
 
	private function layoutPopUp():void
	{
		// match the popups width, height to the View
		mPopUp.width = width;
		mPopUp.height = height;
	}
 
	private function positionPopUp():void
	{
		// use the View x, y coords
		var point:Point = new Point(x, y);
		// convert the View x,y to global so this can be laid out in nested Views
		point = parent.localToGlobal(point);
		// set the popup's global x,y coords
		mPopUp.x = point.x;
		mPopUp.y = point.y;
	}
 
	private function startOperation():void
	{
		mTimer = new Timer(3000, 1);
		mTimer.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
		mTimer.start();
	}
 
	private function endOperation():void
	{
		mPopUp.close();
		mPopUp = null;
	}
 
	private function timerCompleteHandler(event:TimerEvent):void
	{
		mTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
		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>

Whats going on in that code?

  • A Button was added to the actionContent of the ActionBar to trigger the creation of the popup.
  • A handler was added to create the popup and start the progress operation.
  • The createPopUp() method creates an new SkinnablePopUpContainer and sets it’s skinClass, this is how the skin is applied in this version.
    • The backgroundColor and backgroundAlpha styles are there to show how to adjust the popups background.
  • The layoutPopUp() assigns a new width and height based on the View's width and height.
  • The popup is then opened using the open() method that calls the PopUpManager.
  • The positionPopUp() method takes the View's x, y coords and translates them to global coords so the popup will be placed directly on top of the View.
    • This in effect creates a poor mans modal over the View.
  • The startOperation() method simulates a long running operation that in this case runs three seconds and calls the popups close() to dismiss the popup.

Step 4: Run the application

Conclusion

Well, looks the the BusyIndicator is a capable little component of display a familiar interface to mobile users. Do you see anything wrong with Take 1 of the BusyPopUp ?

I do and here are my thoughts;

  • Using a skin for this implementation is not good. Spaghetti code for 1 line of added content.
  • All of the logic for the popup is in a View class, no good, remember separation of duties in design patterns 101?
  • There is no way of adding any type of textual information to let the user know what is going on. (yes we could have easily put a Label in the contentGroup, how to get to it?)

What to do about this;

  • Take 2 will consist of a component developers implementation on solving the above problems.
  • Create a BusyLabelPopUp component that subclasses SkinnablePopUpContainer.
    • This will remove all logic from the view.
  • Use the partAdded() method of the new component to add a BusyIndicator and Label component to the contentGroup.
    • This will remove the need for duplicating the skin when in this case there is really no need for a custom skin.
  • Add a property status on the new component so real time textual updates are possible through the BusyLabelPopUp public interface.
  • Override the updatePopUpPosition() of the BusyLabelPopUp to apply the layout logic of the popup over the owner.

I went out of my way to make a crappy implementation first, so you can learn from the worst. :) In these new times of mobile, applications need to be abstract and decoupled because the likelihood of wanting to reuse code and components increase by a factor of 100.

Take 2 will be published shortly showing the proper creation of components and api.

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/

Categories: AIR Mobile Tags: ,