Support for screen navigation and application session caching for QNX PlayBook apps

If you’ve ever worked with Flex “Hero” mobile components you probably love the way Flex “Hero” handles the screen metaphor. It provides a neat way to encapsulate the UI and business logic for each screen in a class.

On a mobile device, you need to keep in mind that an application can be closed anytime by the operating system. This could be because the OS is trying to free some resources or maybe the battery just died. In either case when the user reopens the app, he expects to see the last screen he was working on as well as any data that was entered. Again, with Flex “Hero” mobile applications all you have to do is to set the sessionCachingEnabled to true and assign the information you want to be persisted to the data property of your views.

This is great; however if you develop applications for PlayBook using the QNX libraries (the native Tablet OS SDK for Adobe AIR) you can’t use Flex “Hero” and, as a result, you don’t have support for the screen metaphor. I wanted this kind of support for an application I plan to build for PlayBook.

Lucky for me, I didn’t have to start from scratch because my fellow evangelist, Piotr Walczyszyn, has already created a small library that does nice tweening between two screens and implements the methods for adding a screen or popping one. So, I took his library and I modified it a little bit. Why did I modify the library? First, I wanted to have session caching capabilities and to be able to push data from one screen to another in the same manner as a Flex “Hero” mobile application. And second, I wanted to make it easier to work with QNX UI components (the original library supported generic ActionScript mobile projects).

Basic usage

To use this library you have to add the library code to your project and create classes that extends org.corlan.asviews.BaseView for each view/screen you need for your application.

In the main application file you create an instance of the org.corlan.asviews.ViewNavigator class. You use its pushView() method to define the first view/screen and to move to new screens. You use the popView() method to move back to the previous screen/view or popToFirstView() to jump directly to the first view/screen.

If you need session caching for your app there are two things you have to do. First, when you create the ViewNavigator instance, you set the second argument to true. Second, you register the ViewNavigator.prepareForClosing() method as an event listener for the application Close event:

 NativeApplication.nativeApplication.addEventListener(
     Event.EXITING, navigator.prepareForClosing, false, 0, true);

See this screencast for more information or read on the next section.

Downloading and using the library

You can download the sample project from here and the library from here.

It is quite simple to use it once you set up the library project and add it as a dependency to your ActionScript mobile project. Here are the steps:

  1. Download the as3viewnavigator library from here and import it in Flash Builder Burrito. Then open the project properties dialig box, select Flex Library Build Path, and make sure that qnx-screen.swc file is added to the build path (you can find this swc in the sdks/blackberry-tablet-sdk-0.9.2/frameworks/libs/qnx-screen/ folder inside the Flash Builder Burrito installation folder).
  2. Create a new ActionScript Mobile Project for PlayBook development. Then open the project properties dialog box, select ActionScript Build Path, click the Add Project, and choose the as3viewnavigator project.

Now you are ready to use it. The basic steps are:

  • For each screen you want, create a class that extends the org.corlan.asviews.BaseView (this class extends the QNX Container class and implements the org.corlan.asviews.IView interface). When you create the screen classes you have to keep in mind that you need to override two methods from the BaseView class:
    • createUI() – this is where you create the UI for the screen using the components from the QNX libraries or your own components.
    • applyData() – you use this method to populate the UI with the data saved in the previous session. This method is called automatically once your screen class is added to the stage.
  • Store whatever data you want to be persisted in the data property of your screen classes
  • In the main application file, you instantiate the org.corlan.asviews.ViewNavigator class and push the first screen. Also, you need to register the ViewNavigator.prepareForClosing method to listen for application Close events.
  • In any screen that you need to push a new screen (or move back) you just call the navigator.pushView(View-Class-Name, data), navigator.popView(), or navigator.popToFirstView() methods

Now let’s see some snippets of code to better understand the workflow. First here is the code for the main application file:

[SWF(height="600", width="1024", frameRate="30", backgroundColor="#FFFFFF")]
public class ASViews extends Sprite
{

	private var navigator:ViewNavigator;

	public function ASViews()
	{
		stage.align = StageAlign.TOP_LEFT;
		stage.scaleMode = StageScaleMode.NO_SCALE;

		// Creating an instance of ViewNavigator
		// if second argument is set to true then the history
		// is persisted between sessions
		navigator = new ViewNavigator(this, true);
		// Pushing first view to the navigator
		navigator.pushView(HomeScreen);
		//listening for application closing event
		//in order to save the history
		NativeApplication.nativeApplication.addEventListener(Event.EXITING,
				navigator.prepareForClosing, false, 0, true)
	}

}

And here is the code for the first screen, HomeScreen (in the onItemSelected() method I push the second screen passing along the current selected item from the list to be used in the second screen):

public class HomeScreen extends BaseView
{

	private var list:List;

	public function HomeScreen(s:Number=100, su:String="percent")
	{
		super(s, su);
	}

	override protected function createUI():void {
		flow = ContainerFlow.HORIZONTAL;

		var optionsArr:Array = [
			{label: "Flash Gordon - The fastest man on earth!"},
			{label: "Superman - This guy could use some fashion advisory!"},
			{label: "Batman - The coolest bat in town!"},
			{label: "Green Lantern - Not a friend of Green Hornet"},
			{label: "Green Hornet - Can be tricked with some good coffee!"}
		];

		//list with the options
		list = new List();
		list.containment = Containment.BACKGROUND;
		list.dataProvider = new DataProvider(optionsArr);
		list.selectionMode = ListSelectionMode.SINGLE;
		list.addEventListener(ListEvent.ITEM_CLICKED,
                        onItemSelected, false, 0, true);

		addChild(list);
	}

	private function onItemSelected(e:ListEvent):void {
		if (!data)
			data = new Object();
		data["selectedItem"] = list.selectedIndex;
		navigator.pushView(SecondScreen, list.selectedItem);
	}

	override protected function applyData():void {
		trace("applyData() HomeScreen");
		if (!data)
			return;
		if (data["selectedItem"])
			list.selectedIndex = data["selectedItem"];
	}
}

This is the code for the second screen, SecondScreen (in the onGoBack() method I call the navigator.popView() method to move back to the previous screen):

public class SecondScreen extends BaseView
{

private var lbl:Label;

public function SecondScreen(s:Number=100, su:String="percent")
{
	super(s, su);
}

override protected function createUI():void
{
	flow = ContainerFlow.VERTICAL;

	var topContainer:Container = new Container();
	//			topContainer.margins = Vector.([1,1,1,1]);
	//			topContainer.debugColor = 0x33FF33;
	topContainer.size = 50;
	topContainer.sizeUnit = SizeUnit.PIXELS;
	topContainer.flow = ContainerFlow.HORIZONTAL;
	topContainer.containment = Containment.DOCK_TOP;

	var left:Container = new Container();
	//			left.margins = Vector.([1,1,1,1]);
	//			left.debugColor = 0x33FF33;
	left.size = 70;
	left.sizeUnit = SizeUnit.PERCENT;
	left.flow = ContainerFlow.VERTICAL;
	left.align = ContainerAlign.NEAR;

	var right:Container = new Container();
	//			right.margins = Vector.([1,1,1,1]);
	//			right.debugColor = 0x33FF33;
	right.size = 30;
	right.sizeUnit = SizeUnit.PERCENT;
	right.flow = ContainerFlow.VERTICAL;
	right.align = ContainerAlign.FAR;

	lbl = new Label();
	lbl.text = "";
	lbl.autoSize = TextFieldAutoSize.CENTER;

	var backBtn:BackButton = new BackButton();
	backBtn.label = "Go Back";
	backBtn.addEventListener(MouseEvent.CLICK, onGoBack, false, 0, true);

	left.addChild(new Spacer(50));
	left.addChild(lbl);
	right.addChild(new Spacer(1));
	right.addChild(backBtn);

	topContainer.addChild(left);
	topContainer.addChild(right);
	addChild(topContainer);
}

private function onGoBack(e:MouseEvent):void {
	navigator.popView();
}

override protected function applyData():void {
	trace("applyData() SecondScreen");
	if (!data)
		return;
	lbl.text = data["label"];
}

In case you’re wondering how do I save the data, I’m using SharedObject and I persist an array representing the current screen stack (the screen class name and the data object for each instance).

Conclusions

Although I intend to use this library in my projects, I haven’t tested it extensively ywt. This means you may find bugs. If so, please let me know. Furthermore, there is a big difference in how this library manages the views compared to how Flex does: Flex’s ViewNavigator destroys by default a view when removing it. This library doesn’t. So, if you have lots of views with lots of components you might end up using a lot of memory. For such a use case it should be modified to mimic the Flex approach.

If you’ve started to create apps for PlayBook I’m eager to learn how you manage the screens. So, please talk back :D!

Update for Flash Player 10.2 Support on Tablets

Adobe will offer Flash Player 10.2 pre-installed on some tablets and as an OTA download on others within a few weeks of Android 3 (Honeycomb) devices becoming available, the first of which is expected to be the Motorola Xoom.

We are excited about the progress we’ve made optimizing Flash for tablets, alongside partners including Motorola, and expect our momentum to continue. As we announced last week, over 20 million smartphones were shipped or upgraded with Flash Player in 2010 and over 150,000 consumers on the Android Market are rating it 4.5 out of 5 stars. We have raised our estimates for 2011 and expect to see Flash installed on over 132 million devices by the end of this year. Consumers are clearly asking for Flash support on tablet devices and the good news is that they won’t have to wait long. We are aware of over 50 tablets that will ship in 2011 supporting a full web experience (including Flash support) and Xoom users will be among the first to enjoy this benefit.

**UPDATE (3/18/11): We’re happy to announce that Flash Player 10.2 is now available for download on Android Market. To see if your device is certified for Flash Player 10.2, go to http://www.adobe.com/go/cd1. Note that this is a production GA (General Availability) release for Android 2.2 (“Froyo”) and 2.3 (“Gingerbread”) devices and an initial beta release for Android 3.0.1+ (“Honeycomb”) tablets that include Google’s system update (from 3.0 to 3.0.1).

We’ve been collaborating closely with Google and the beta of Flash Player 10.2 for Android 3.0.1+ is an exciting release offering a full web browsing experience, including video, games and other interactive content, to the latest Android tablets. A production GA version of Flash Player 10.2 for Android 3.0.1+ is expected in the coming weeks.

For more details about Flash Player 10.2 performance enhancements, features and capabilities, please check out the Flash Player Team Blog post.

NativeApplicationUpdater 0.5.0 released

Today I published a new version (0.5.0) of NativeApplicationUpdater library to Google Code project. This is a minor, mostly bug fix release: Configuring proper XML settings when parsing plist in HdiutilHelper class (based on contribution from Erik Pettersson) Update file is downloaded into a temporary folder with its name parsed out of the download url If […]

Adobe Refresh – Australia, Seoul, Hong Kong and Singapore

I’m really excited to be joining up with the crew for Adobe Refresh, which will cover some of the latest and greatest features for the Flash Platform as well as show off Adobe’s HTML story over a couple of weeks in March. We’re hitting a total of 6 cities in Australia, Hong Kong, Seoul, and Singapore. I’ll be tagging along with Richard Galvan, Paul Burnett, and Michael Stoddart.

The sessions are going to cover Flash Pro, HTML5, Flex/Flash Builder, Digital Publishing, and a Q&A for answering any questions you might have about the Adobe stack. Plus we’ll be showing off some things that most people won’t have seen live yet, so if you come, you’ll get a first crack at seeing some very cool stuff coming up from Adobe.

Here’s the full list of cities:

I’m looking forward to being able to see all of the community members in those countries as well as getting to show off the latest and greatest with the Flash Platform. I’ll be talking about multiscreen development with Flex and Flash Builder and there is some VERY good stuff for developers on the horizon when it comes to building apps across devices. This will be a great event to see it all come together.

MWC wrap-up: my favorite devices

While sitting in the airport waiting for my flight home from Mobile World Congress I thought it was a good idea to sum up some of my favorite MWC announcements. One thing that definitely stood out was Android. The little green robot was everywhere! Google provided all their partners with Android candy and a robot display. This was also the first time that Google had a booth at MWC and it was the talk of the town! It was one of the biggest booths on the show floor and was always crowded. I’m guessing the slide and the giveaways had something to do with that.

Google asked a bunch of developers and partners to showcase their Android app on their booth and I think that really worked. Adobe partner Condé Nast showed off Wired Magazine and The New Yorker on a Motorola Xoom on the Android booth. The electronic versions of Wired and The New Yorker are built with Adobe’s Digital Publishing Suite.

MWC was full of tablets. The Motorola Xoom, Samsung Galaxy Tab 10.1 and the LG OptimusPad all look very interesting. NVidia even showed the LG OptimusPad with Flash Player 10.2 on Honeycomb! My favorite was definitely the HTC Flyer. Well… That and the PlayBook.

I really like the HTC Sense UI and HTC seems to have done a fantastic job at building a tablet specific version of the Sense UI. Sadly they were only showing it behind glass so they probably still have a bit of work to do. The specs look great though and this is high on my wish list. I also like BlackBerry’s PlayBook. RIM made a balsy move by opting for a brand new OS instead of picking one that already exists. And you know what… It works… The PlayBook is incredibly fast and the UI brings a nice breath of fresh air to the soon to be crowded tablet market. It’s completely different from anything else on the market. I also like the touch enabled bezel on the PlayBook.

There were also plenty of new smartphones at MWC. The ones that really popped out were the Samsung Galaxy S2, the LG Optimus 2X and the HTC Incredible S. The Galaxy S2 is incredibly light. It’s so light that it felt like there was no battery inside. The LG Optimus 2X is also very light and it runs on NVidia’s Tegra 2 making it incredibly fast. My favorite was again an HTC device. The Incredible S looks more or less like what I have in my Desire HD except that it’s faster and has a front facing camera.

Oh… I should probably say that all of the devices mentioned in this post have Flash Player preinstalled and there were dozens of other devices that also come with Flash Player preinstalled. These are just my favorites. It’s been an amazing show and I already look forward to next year’s edition.

Progressive download is dead. Long live on-demand HTTP dynamic streaming. Stream FLV/F4V/MP4 files to Flash over HTTP.

When you view content on YouTube, you don’t have to wait for the content to download to skip ahead to the end of the file. Progressive download is dead, this is on-demand HTTP streaming.

Use Adobe HTTP Dynamic Streaming to serve on-demand video over HTTP on your web site.

  1. Download the File Packager
  2. Download the Apache HTTP Origin Module
  3. Install the HTTP Origin Module to Apache HTTP Server 2.2
  4. Use the File Packager off-line tool to package an FLV, F4V, or MP4 file for HTTP streaming.
  5. Copy the packaged files to the Apache webroot folder.
  6. Play the files in Flash Media Playback or OSMF Sample Player for HTTP Dynamic Streaming.

These are the high-level steps, for the nitty-gritty, check out the On-demand HTTP Dynamic Streaming tutorial.

To stream live media over HTTP to Flash/AIR, use Flash Media Server. Check out the Live HTTP Dynamic Streaming tutorial in the FMS Developer’s Guide.

Working with PlayBook QNX UI components

In a previous post I discussed about all sort of lists available to developers when using the BlackBerry Tablet OS SDK for AIR. In this article I will focus on the remaining UI controls. I have an idea for a PlayBook application and before writing a single line of code I wanted to make sure I understand all the UI components that are available as part of the BlackBerry SDK (by the way, in case you didn’t know, RIM offers a free PlayBook device to any developer who gets his application on BlackBerry App World). And I thought it is worth sharing with you my little research.

I won’t talk about how to skin these components. This is a topic for another blog post. In the mean time you can read Ryan’s posts on skinning ( buttons/text fields and lists) and Renaun’s post.

Buttons

There are seven buttons in the package qnx.ui.buttons. When you want to do something when a buttons was clicked you have to register an event listener for the MouseEvent.CLICK, Event.CHANGE (only for SegmentedControl), or Event.SELECT (for ToggleSwitch):

myButton.addEventListener(MouseEvent.CLICK, doSomethingOnClick);

private function doSomethingOnClick(e:MouseEvent):void {
    //do something
}

LabelButton and IconButton, as the names sugest, enables you to create buttons with either text labels or icons. Both these buttons extend Button class (if you use the Button class you will get a button without labe or icon). You can enable toggle by setting the the property name with the same name to true. This will give you a button that once clicked stays selected and then you can click again to unselect. In the image below you can see these two buttons with toggle on in selected and unselected state.

You can use a flash.Text.TextFormat object to change the label settings for a LabelButton. You set the image to be used by the IconButton by calling setIcon() method and passing the path to image to be used or a Bitmap/BitmapData instance. Here is the code used for rendering the example above:

var format:TextFormat = new TextFormat();
format.font = "BBAlpha Sans";
format.size = 22;
format.color = 0x990000;
format.align = TextFormatAlign.CENTER;

var btn:LabelButton = new LabelButton();
btn.size = 50;
btn.sizeMode = SizeUnit.PIXELS;
btn.label = "Label Button";
btn.toggle = true;
btn.setTextFormatForState(format,SkinStates.DISABLED);
btn.setTextFormatForState(format,SkinStates.UP);
btn.setTextFormatForState(format,SkinStates.DOWN);
btn.setTextFormatForState(format,SkinStates.SELECTED);
btn.setTextFormatForState(format,SkinStates.DISABLED_SELECTED);
container.addChild(btn);

var btn2:IconButton = new IconButton();
btn2.size = 100;
btn2.sizeMode = SizeUnit.PIXELS;
btn2.setIcon("assets/icon.png");
btn2.toggle = true;
container.addChild(btn2);

BackButton extends the LabelButton and you can set a label if you want (the default label is Back). Also has a arrow icon to the left of the button. Like in the case of LabelButton you can use a TextFormat object and played with the toggle property.

var btn3:BackButton = new BackButton();
btn3.label = "Go Back";
container.addChild(btn3);

CheckBox button extends the LabelButton too and has the toggle property set to true (you can’t change this one). However, you can control where to display the text relative to the check sign by using labelPlacement property. Using labelPadding property you can set the space between the label and icon. And of course TextFormat can be used to customize the label look. You have to pay attention to the width property if you don’t want to have the label cropped.

var cb:CheckBox = new CheckBox();
cb.label ="Select Me";
cb.width = 150;
cb.labelPlacement = LabelPlacement.RIGHT;
cb.labelPadding = 30;
container.addChild(cb);

The last component that extends the LabelButton is RadioButton. You use this component to force the user make a single selection from a group of at least two radio buttons. You can use TextFormat to custom the look of the label. You assign a radio button to a group by setting the same string to the groupname property. If you want to have one option preselected, you set the selected property to true.

var rb1:RadioButton = new RadioButton();
rb1.size = 30;
rb1.sizeUnit = SizeUnit.PIXELS;
rb1.groupname = "group1";
rb1.label = "Yes";
rb1.selected = true;
container.addChild(rb1);

var rb2:RadioButton = new RadioButton();
rb2.size = 30;
rb2.sizeUnit = SizeUnit.PIXELS;
rb2.groupname = "group1";
rb2.label = "No";
container.addChild(rb2);

SegmentedControl displays a series of connected radio buttons and the user can select only one of them. Like in the case of the lists you use a DataProvider to add the labels you want. There is an alternative to add a label one by one using the  addItem() or addItemAt() methods. Each label is set using an instance of Object with one property named label. You can preselect an option using the selectedIndex property and you listen for user selection by registering an event listener for the flash.events.Event.CHANGE event. You can skin this component by setting up a new skin for the background and buttons.

var segData:Array= [
		{label:"< 10"},
  		{label:"10-19"},
  		{label:"> 20"}
	];
var seg:SegmentedControl = new SegmentedControl();
seg.dataProvider = new DataProvider(segData);
seg.selectedIndex = 1;
container.addChild(seg);

The last button is the ToggleSwitch – this component extends the Slider component. This ones enables you to create a button with two labels and slider that always hides one of this two labels. To set the label from the left you use selectedLabel property, and for the one on the right you use defaultLabel property. You can control the default selection by setting selected property to true/false and you react to component changes by listening for Event.SELECT events. You can see in the image below the component using the default skin in the two states:

var tog:ToggleSwitch = new ToggleSwitch();
tog.selectedLabel = "On";
tog.defaultLabel = "Off";
tog.selected = true;
container.addChild(tog);

Text Controls

In the package qnx.ui.text you’ll find two UI components: Label and TextInput. You can use the Label component for displaying text (can’t receive focus). There are a number of properties you can use in order to fine control how the text will be displayed:

  • By default a label has 100 pixels width. If you don’t set the autoSize property or adjust the width, then it is quite possible to not see the whole text. The easiest way is to use the autoSize property and set how the label should grow so the text will be fitted: label.autoSize = TextFieldAutoSize.CENTER;
  • If you want to set the font family name/size/type-face/color you use an instance of TextFormat to set these things and then you assign it to the label’s format property
  • You set the text you want to be displayed using either the text property or htmlText property. After playing with the htmlText property it seems that the main benefit is that you can throw some HTML formatted text and it will be displayed just like text – this means you want see the tags in the label.
  • When you set the multiline property to true, and display a HTML text like this <p>one</p><br/><p>two</p> then you will see the text on two paragraphs and with one extra line between them
  • selectable property when set to true allows user to select the text
  • You can use the wordWrap property to allow wrapping the words so the text can be displayed without increasing the width of the label

Here is a code example and its rendering:

var format:TextFormat = new TextFormat();
format.font = "BBAlpha Sans";
format.bold = true;
format.size = 19;
format.color = 0x000099;

var lbl:Label = new Label();
lbl.format = format;
lbl.autoSize = TextFieldAutoSize.CENTER;
lbl.wordWrap = true;
lbl.multiline = true;
lbl.selectable = true;
lbl.htmlText =
"<p><strong>This is a label!!!</strong></p><br/><p>Second Paragraph</p>";
container.addChild(lbl);

If you want to let the user introduce some text or edit it, then you’ll use the TextInput component. This is component is pretty powerful because it lets you add custom icons inside of it (on the left/right sides) and control when to be displayed. You can also control what type of the keyboard should be displayed when the text field gets the focus (the keyboard is pulled out automatically when a TextInput is selected). Just set the keyboardType property to one of the following values: KeyboardType.DEFAULT, KeyboardType.EMAIL, KeyboardType.URL, KeyboardType.PIN, or KeyboardType.PHONE. You can customize the label displayed on the Enter key by using the returnKeyType property: txt.returnKeyType = ReturnKeyType.GO. If you want to use the text field for passwords you can set the displaysAsPassword property to true.

When you want to format the text you use a TextFormat object exactly like in the case of the Label. By default you can have a Clear icon inside of the text field you can use for deleting the whole text. You can control when to be displayed using the clearIconMode property: txt.clearIconMode = TextInputIconMode.ALWAYS. If you want to add custom icon to the left or right side, one way to do it is to create a transparent PNG file with the icon you want and then embed the PNG file and set the class to the leftIcon or rightIcon properties. From my tests it seems that is better to have the icon rather bigger than smaller compared to what will be actually rendered. And you can control when to be displayed using leftIconMode or rightIconMode properties. For example:

[Embed(source="assets/arrow48x48.png")]
private var ArrowIcon:Class;
...
txt.leftIcon = new ArrowIcon();
txt.leftIconMode = TextInputIconMode.UNLESS_EDITING;

Here is the code used to create the example displayed in the pictures:

var format:TextFormat = new TextFormat();
format.font = "BBAlpha Sans";
format.bold = true;
format.size = 19;
format.color = 0x000099;

var txt:TextInput = new TextInput();
txt.format = format;
txt.width = 400;
txt.keyboardType = KeyboardType.PHONE;
txt.returnKeyType = ReturnKeyType.GO;
txt.clearIconMode = TextInputIconMode.ALWAYS;
txt.leftIcon = new ArrowIcon();
txt.leftIconMode = TextInputIconMode.UNLESS_EDITING;

If you want to do something when the user introduces or edits the text, then you have to register an event listener for Event.CHANGE event:

txt.addEventListener(Event.CHANGE, onChange);

Here are the shortcomings I found so far for the TextInput (as of writing this post the SDK is in beta):

  1. You can’t test it outside of the Emulator without a hack – see Renaun’s post on this topic. This is happening because this component requires a class that is part of the RIM’s AIR runtime.
  2. When setting the autoSize property to TextFieldAutoSize.CENTER you can’t select the field and type in.
  3. It doesn’t support multilines. Some people found a way to get around this, but I think we need official support from RIM in a form of a different component or an addition to the TextInput.

Sliders

In the package qnx.ui.slider you’ll find a Slider and VolumeSlider components. VolumeSlider extends the Slider and provides a default skin suited for media applications. You can set the minimum and maximum values using the properties with the same name. You can’t set the step when moving the thumb around. You can retrieve the current value using the value property and keep in mind it is a Number and not Integer (so you can get 6.7 for example). If you want to have the slider’s thumb position to something else than the left side by default, you set the value property to what you need.

var slider:Slider = new Slider();
slider.width = 300;
slider.height = 40;
slider.minimum = 1;
slider.maximum = 10;
slider.value = 5.5;
slider.addEventListener(SliderEvent.START, onDragStart);
slider.addEventListener(SliderEvent.END, onDragEnd);
slider.addEventListener(SliderEvent.MOVE, onMove);

var volume:VolumeSlider = new VolumeSlider();
volume.minimum = 0;
volume.maximum = 1;

While you can set the width of the component, setting the height doesn’t change anything. You need to create skins for thumb and track if you need more customization and use setThumbSkin() and setTrackSin() methods to apply them.

There are three events you can listen for: SliderEvent.START (when the thumb starts to move), SliderEvent.END (when you finished the dragging), and SliderEvent.MOVE (while the thumb is moving). It seems that both these sliders can be only horizontal.

Progress Bars

When you want to display progress to the user you can use components available in the qnx.ui.progress package. The ActivityIndicator component lets you display a visual feedback when a time consuming operation is executing. You start the animation by calling the animate(true) method and stop it with animate(false). You can check if the animation is running by calling isAnimating() method. Should be possible to skin by extending ActivityIndicatorSkin.

var activity:ActivityIndicator = new ActivityIndicator();
activity.setSize(30, 30);
activity.animate(true);

Using the ProgressBar and PercentageBar you can give a sense of progress, of how much of the work has been completed and how much is remaining. You change this by setting the progress property to a number between 0 and 1 where 1 represents 100% of the job done. PercentageBar gives you a label as well, so the user knows exactly where he is – you can hide it by setting showPercent property to false.

var prog:ProgressBar = new ProgressBar();
prog.width = 200;
prog.progress = 0.3;

var per:PercentageBar = new PercentageBar();
per.width = 200;
per.progress = 0.9;

Extending the skins from the qnx.ui.skins.progress package is the way to customize the look and feel.

Image Controls

In the qnx.ui.display you’ll find two components: Image and TilingBackground. You use Image class when you want to display an image. By calling the setImage() method you set a local path to an image, an URL, or a Bimap/BitmapData object. There are two events you can listen for:

  • Event.COMPLETE – this event is triggered once the image was loaded and ready to use
  • IOErrorEvent.IO_ERROR – dispatched when the image can’t be loaded

If you plan to scale the image (up or down) you might want to set the smoothing property to true. You can cache the images by creating a single instance of ImageCache and assign it to Image.cache property. You can force the cache to reload an image if you need.

Here is an example that loads an image from the application folder:

private static var imageCache:ImageCache = new ImageCache();
private static const IMAGE_PATH:String =  "assets/picture.jpg";
...
var img:Image = new Image();
img.smoothing = true;
img.cache = imageCache;
img.addEventListener(Event.COMPLETE, onComplete);
img.setImage(IMAGE_PATH);
container.addChild(img);
...
private function onComplete(e:Event):void {
    container.layout();
}

Please note that in the above example I didn’t explicitly set the width and height of the image. These were set automatically to the values taken from the loaded image. If you want to set them to something different you have to do it when the image was loaded and the Event.COMPLETE event was thrown. Otherwise they will be overwritten.

When you want to create a tile from images, you use the TilingBackground component. You don’t have much control on how image will be tiled (it will be tiled on both X and Y depending on how much space it is available and how large the image to be tiled is). What was occurred to me was the way you set the image. I was expecting to have the same options available like for Image.setImage(). You use bitmapData property and set to a BitmapData object. In the following example I’m using the ImageCache.getImage() API to get the BitmapData object loaded by the Image object and set it to the bitmapData property of the TilingBackground:

tile = new TilingBackground();
tile.containment = Containment.BACKGROUND;
container.addChild(tile);
//inside the complete event handler of the Image
tile.bitmapData = imageCache.getImage(IMAGE_PATH, false);

Dialog

QNX UI library provides a number of classes for creating a floating window on top of your application. I think that it is a nice feature, because with the real estate available on the PlayBook tablet you may want to use one of these dialogs instead of “pushing” an entirely new screen (screen metaphor approach).

There are five dialog boxes to choose from, and you have to remember that the appearance is controlled by the BlackBerry Tablet OS. You can control the position where the window will be placed and the size as well the type of the window: system modal (blocks the entire tablet until the window is closed – Dialog.open() ) or application modal (blocks the interaction only with the application – Dialog.open(IowWindow.getAirWindow().group) ). Using the DialogAlign and the align property of the dialog you can specify where on the vertical should be placed (on horizontal it is always centered). You can add buttons and set properties to them like this:

var myDialog:AlertDialog = new AlertDialog();
myDialog.addButton("Yes");
myDialog.addButton("No");
myDialog.addButton("Cancel");
myDialog.setButtonPropertyAt("enabled", false, 2);

Once the user clicks on one of the buttons you add to the window, if you registered an event listener on the dialog for the Event.SELECT event you can check example on what button he pressed:

myDialog.addEventListener(Event.SELECT, alertButtonClicked);
...
private function alertButtonClicked(e:Event):void {
	trace((e.target as AlertDialog).selectedIndex)
}

The live cycle for a dialog window is:

  1. You create a new instance of the dialog you want
  2. You configure the content (messages, buttons, labels…) and the positioning/sizing of the dialog
  3. You register an event listener for the Event.SELECT event
  4. You call the show() method on the dialog to display it

AlertDialog is the simplest window. You use this window in order to display some text to the user and allow him to click on one of the buttons you add to it. You set the title and message using the properties with the same names.

And here is the code:

var myDialog:AlertDialog = new AlertDialog();
myDialog.title = "AlertDialog";
myDialog.message = "This is an alert dialog. Do you like it?";
myDialog.addButton("Yes");
myDialog.addButton("No");
myDialog.addButton("Cancel");
myDialog.setButtonPropertyAt("enabled", false, 2);
myDialog.dialogSize = DialogSize.SIZE_MEDIUM;
myDialog.align = DialogAlign.TOP;
myDialog.addEventListener(Event.SELECT, alertButtonClicked1);
myDialog.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked1(e:Event):void {
	trace((e.target as AlertDialog).selectedIndex)
}

LoginDialog presents the user an interface for providing the information necessary for a login operation: user name, password, remember me. It extends the AlertDialog class so everything you can do with it applies to LoginDialog too. Using the rememberMe, password, and username properties you can retrieve the information the user introduced. You can set the texts used for username/password/remember me using these properties: usernameLabel, passwordLabel, passwordPrompt, and rememberMeLabel.

var login:LoginDialog = new LoginDialog();
login.title = "Login";
login.message = "Please enter your username and password:";
login.addButton("OK");
login.addButton("Cancel");
login.passwordPrompt = "password";
login.rememberMeLabel = "Remember me";
login.rememberMe = true;
login.dialogSize = DialogSize.SIZE_SMALL;
login.addEventListener(Event.SELECT, alertButtonClicked2);
login.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked2(e:Event):void {
	var login:LoginDialog = e.target as LoginDialog;
	trace(login.password); //retrieve the password
	trace(login.username); //retrieve the username
	//retrieve whather the remember me option was selected
	trace(login.rememberMe);
	trace(login.selectedIndex); //retrieve the button was clicked
}

PasswordChangeDialog, as the name suggests, is handy when you want to let the user change his password. It extends the LoginDialog. As with its superclass, you can customize the labels of the predefined UI components.

Here is the code to create the dialog from above:

var password:PasswordChangeDialog = new PasswordChangeDialog();
password.title = "Password Change";
password.message = "Please choose your new password:";
password.addButton("OK");
password.addButton("Cancel");
password.dialogSize= DialogSize.SIZE_MEDIUM;
password.addEventListener(Event.SELECT, alertButtonClicked3);
password.show(IowWindow.getAirWindow().group);

And here is how you retrieve the user input:

private function alertButtonClicked3(e:Event):void {
	var login:PasswordChangeDialog = e.target as PasswordChangeDialog;
	trace(login.password); //retrieve the old password
	trace(login.username); //retrieve the username
	trace(login.newPassword); //retrieve the new password
	trace(login.confirmation); //retrieve the confirmation for the new password
	trace(login.selectedIndex); //retrieve the button was clicked
}

The PromptDialog extends AlertDialog and provides a way to capture user text input. You can set the hint to be used by the TextField using the prompt property. And you can retrieve the text entered using the text property.

Here is the code:

var prompt:PromptDialog = new PromptDialog();
prompt.title = "Choose your username!";
prompt.message = "Please enter an username between 6-10 chars:";
prompt.prompt = "username";
prompt.addButton("OK");
prompt.addButton("Cancel");
prompt.dialogSize= DialogSize.SIZE_SMALL;
prompt.addEventListener(Event.SELECT, alertButtonClicked4);
prompt.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked4(e:Event):void {
	var d:PromptDialog = e.target as PromptDialog;
	//retrieve the text introduced by the user
	trace(d.text);
}

The last dialog window is the PopupListcomponent. This dialog creates a window that presents a scrollable list and the user can select either a single entry or multiple entries. You set the items that will be rendered in the list by assigning an Array of strings to the items property. If you want to allow multiple selection then you set multiSelect property to true. If you want to preselect an item or a number of items, just set the selectedIndices property to an array of indices of the items you want selected.

Here is the code:

var popUp:PopupList = new PopupList();
popUp.title = "Chose your title:";
popUp.items = ["Mr.", "Ms.", "Miss", "Dr.", "Phd"];
//allow multiple selection
popUp.multiSelect = true;
//preselect the second item
popUp.selectedIndices = [1];
popUp.addButton("OK");
popUp.addButton("Cancel");
popUp.dialogSize= DialogSize.SIZE_SMALL;
popUp.addEventListener(Event.SELECT, alertButtonClicked5);
popUp.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked5(e:Event):void {
	var d:PopupList = e.target as PopupList;
	//retrieve the selected index/indices
	trace(d.selectedIndex);
	trace(d.selectedIndices.toString());
}

One last note on dialog components: it doesn’t seem you can skin them or extend. It would have been nice to be able to create custom dialog with the components you want in them. On the other hand they really cover most use cases you might think of.

Download

If you want to have all the code used for this article, just download this ActionScript mobile project for Flash Builder 4.5 (you’ll need the PlayBook simulator and the BlackBerry plug-in for Flash Builder installed in order to run and compile the code).

Conclusions

These are not all the components available in the QNX libraries. Though I feel that with this post and the previous one on lists I covered the vast majority. Check Ryan‘s and Renaun‘s blogs for articles on Containers and playing multimedia.

I have to say that I really think that RIM did an amazing job with this QNX UI library. I thing there is pretty much all you need to create great applications. Especially the list components seemed to be almost complete and the Dialog components are a nice touch especially for tablet devices (though I’m not sure how well they will perform on smartphone screens).

In general everything seems to be well engineered and design. The biggest caveat of all this is the fact we don’t have the library source code. Any Flex developer will tell you how important is the fact that the Flex framework is open source and you can inspect all the code you wish. It’s great when learning the framework and it is definitely great when debugging. Right now even a process that shouldn’t be that complicated (skinning) is a try and error process.

What do you think?

Digital magazines coming to Android built on AIR

I’m sure you’ve heard about the Wired, Martha Stewart, and New Yorker magazines on iPad, right? Those were created with a new suite of tools that we’ve been working on. The Digital Publishing Suite allows you to use your existing staff, skills, and Creative Suite publishing tools to design and deliver publisher-branded reading experiences to mobile devices.

Currently these magazines were only available on iOS devices but this week at MWC we announced that the Android viewer is now available. The application is actually built on AIR!

Condé Nast, publisher of WIRED magazine, showed off the magazine at MWC on the Motorola Xoom running Honeycomb. Check out the video below for more info (courtesy Seattle Times):

MWC 2011 and Flash Platform: Good Progress and Good Performance

With Mobile World Congress coming to a close in Barcelona, we are seeing tremendous momentum for the Flash Platform runtimes on mobile devices. It’s incredibly exciting to see, touch and play with all the latest devices that our ecosystem partners are announcing and launching this year, including tablets like the Motorola XOOM, RIM Blackberry PlayBook and Samsung Galaxy Tab 10.1 and smartphones like the Samsung Galaxy S II, Sony Ericsson Xperia pro and neo and the five new Android smartphones from HTC. With beautiful web content for Flash Player and rich apps built with AIR, these devices highlight the wide adoption of both the Flash Player and AIR, especially since the former has only been available for about 6 months and AIR has only been available for a little more than 3 months!

While the momentum has been astonishing, there are still some questions on how Flash Player is performing on mobile devices. Tim Siglin, an editor at Streaming Media and co-founder of Transitions, Inc., wanted to find out for himself and published his findings in his whitepaper Performance or Penalty – Assessing Flash Player 10.1 Impact on Android Handsets. It is an in-depth look at the performance of Flash Player on a number of mobile devices, and the results may surprise you. Key highlights from the whitepaper include:

  • For the vast majority of video content delivered for Flash Player on mobile devices, performance is equivalent to the full frame rate experience on desktop. This is a huge improvement vs. video played back on previous devices.
  • The most significant factors impacting mobile battery life for video playback, for both Flash Player and the native device player, is appropriate video encoding and optimization.
  • There is minimal, if any, impact on mobile device battery life with Flash Player, even with multiple apps running.
  • All web content, running in Flash Player or not, consume battery power at consistent rates over WiFi in the native browser.
  • GPS, 3G and other resources on a phone consume more power than Flash Player, including when highly interactive content is viewed.
  • Flash Player 10.1 performance was 350% better than equivalent content in HTML, running an average of 24 frames per second for Flash Player 10.1 and 7 fps for HTML.

These initial findings support the positive feedback we have seen from users on Android Market where there have been over 6M downloads, 150K ratings resulting in a 4.5 out of 5.0 stars for Flash Player. Here are a few additional new devices that were announced yesterday at Mobile World Congress that are supporting the Flash Platform runtimes: