Christophe Coenraets
Adobe Technical Evangelist
Blog: http://coenraets.org
Twitter: @ccoenraets
In this tutorial, you use Adobe® Flash Builder® 4.5 to build a simple, yet fully functional employee directory application for the BlackBerry® PlayBook™ tablet.
You don’t need a BlackBerry PlayBook tablet to complete this tutorial: you can use the BlackBerry PlayBook tablet simulator to run and debug the application.
The Employee Directory application allows you to:
Before you start, you will have to download and install the following products:
Download and installation instructions are provided below.
The BlackBerry Tablet OS SDK can be installed “standalone” or as a plug-in on top of Flash Builder Burrito. In this tutorial, we use the plug-in approach for an integrated development environment.
After installing the BlackBerry Tablet OS SDK, the BlackBerry PlayBook Simulator will be located in the BlackBerryPlayBookSimulator-x.x.x folder of your SDK directory. In order to launch the simulator, you will need to open the file BlackBerryPlayBookSimulator.vmdk from within the folder using VMware® Player.
Please follow the below instruction to manually download and install the BlackBerry PlayBook Simulator.
Download FlexPlayBook90Minutes.zip using the link in the Attachments section at the bottom of this article and unzip the file anywhere on your file system.
In this section, you build a simple mobile application that shows a list of employees.
<?xml version="1.0" encoding="utf-8"?>
<components:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" xmlns:components="spark.components.*"
title="Home" creationComplete="srv.send()">
<fx:Declarations>
<s:HTTPService id="srv" url="assets/employees.xml"/>
</fx:Declarations>
<s:List id="list" top="0" bottom="0" left="0" right="0"
dataProvider="{srv.lastResult.list.employee}"
labelField="lastName"/>
</components:View>
Running the application “On the desktop” allows you to quickly test and debug your application without deploying it on the BlackBerry PlayBook or the BlackBerry PlayBook simulator. The “On the desktop” option is not a real emulator: it simply simulates the actual device resolution.
Step 4: Run the Application on the BlackBerry PlayBook Simulator
In this section, you define an item renderer for the list of employees. An item renderer is used to define how each item in a list is visually presented to the user. Spark Item renderers are lightweight classes optimized for mobile devices.
<s:List id="list" top="0" bottom="0" left="0" right="0"
dataProvider="{srv.lastResult.list.employee}">
<s:itemRenderer>
<fx:Component>
<component:IconItemRenderer label="{data.firstName {data.lastName}" messageField="title" />
</fx:Component>
</s:itemRenderer>
</s:List>
2. Run and test the application. The application should look like this:
In this section, you create an EmployeeDetails view that shows the details of the employee selected in the list. You learn how to navigate and pass information between views.
<?xml version="1.0" encoding="utf-8"?>
<components:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" xmlns:components="spark.components.*"
title="Employee Details">
<s:HGroup verticalAlign="middle" gap="12">
<s:Image source="assets/pics/{data.picture}"/>
<s:VGroup>
<s:Label text="{data.firstName} {data.lastName}"/>
<s:Label text="{data.title}"/>
<s:Label text="{data.department}"/>
<s:Label text="{data.city}"/>
</s:VGroup>
</s:HGroup>
</components:View>
Step 2: Opening the Details View
Open EmployeeDirectoryHomeView.mxml and provide the List with a change handler used to open the details view for the selected employee:
<s:List id="list" top="0" bottom="0" left="0" right="0"
dataProvider="{srv.lastResult.list.employee}"
change="navigator.pushView(EmployeeDetails, list.selectedItem)">
<s:itemRenderer>
<fx:Component>
<components:IconItemRenderer
label="{data.firstName} {data.lastName}"
messageField="title"/>
</fx:Component>
</s:itemRenderer>
</s:List>
Step 3: Run the Application
Select an employee in the list: an employee details view should appear for the selected employee.
Part 4: Creating an Action Bar
In this section, you provide the Employee Directory with an Action Bar:
<components:navigationContent>
<s:Button icon="@Embed('assets/home.png')"
click="navigator.popToFirstView()"/>
</components:navigationContent>
Step 2: Creating a Search Bar
<components:titleContent>
<s:TextInput id="key" width="100%"/>
</components:titleContent>
<components:actionContent>
<s:Button icon="@Embed('assets/search.png')" click="srv.send()"/>
</components:actionContent>
With this initial implementation, clicking the search button returns all the employees no matter what you type in the search field. You implement a working search capability in Part 6.
Note that both the EmployeeDetails and the EmployeeDirectoryHomeView views inherit the Home button defined in EmployeeDirectory.mxml. Although it is generally a good idea for all the views of the application to have a Home button, it is superfluous (and potentially confusing) for the Home view of the application to have a Home button.
<components:navigationContent/>
2. Run and test the application.
Note that when you open the details view for an employee, and then go back to the list using the back button of your device (or the home button of the application), the list is empty. This is because the previously active view is automatically destroyed when another view becomes active. When you click the back button, the previous view is actually re-instantiated.
Although a view is destroyed when it becomes inactive, its “data” attribute is persisted and re-assigned when the view is re-instantiated.
To persist the search results leveraging the data attribute:
1. Add a result event handler to the HTTPService in which you assign the lastResult of the HTTP service invocation to the data attribute of the view.
<s:HTTPService id="srv" url="assets/employees.xml"
result="data=srv.lastResult.list.employee"/>
2. Bind the List to data attribute of the view.
<s:List id="list" top="0" bottom="0" left="0" right="0"
dataProvider="{data}"
change="navigator.pushView(EmployeeDetails, list.selectedItem)">
<s:itemRenderer>
<fx:Component>
<components:IconItemRenderer
label="{data.firstName} {data.lastName}"
messageField="title"/>
</fx:Component>
</s:itemRenderer>
</s:List>
3. Run and test the application.
Part 5: Integrating with the Device Capabilities
In this section, you allow the user to call, text, or email an employee from within the application.
<fx:Script>
<![CDATA[
]]>
</fx:Script>
2. Inside the new <fx
cript> block, define a bindable ArrayCollection to hold the list of actions available for the selected employee:
[Bindable]
protected var actions:ArrayCollection;
Note: Make sure you import the ArrayCollection class for this code to compile:
import mx.collections.ArrayCollection;
3. Define the following embedded icons. You’ll use them in the action list itemRenderer.
[Embed("assets/sms.png")]
private var smsIcon:Class;
[Embed("assets/phone.png")]
private var phoneIcon:Class;
[Embed("assets/mail.png")]
private var mailIcon:Class;
4. Override the setter for the “data” attribute of the view to populate the action list with the actions available for the employee based on the available data. For example, an “SMS” action should only be presented to the user if the mobile phone number is available.
override public function set data(value:Object):void
{
super.data = value;
actions = new ArrayCollection();
if (data.officePhone)
{
actions.addItem({type: "tel", name: "Call office",
details: data.officePhone, icon:phoneIcon});
}
if (data.cellPhone)
{
actions.addItem({type: "tel", name: "Call mobile",
details: data.cellPhone, icon:phoneIcon});
actions.addItem({type: "sms", name: "SMS",
details: data.cellPhone, icon:smsIcon});
}
if (data.email)
{
actions.addItem({type: "mailto", name: "Email",
details: data.email, icon:mailIcon});
}
}
4. Display the list of actions: Below the closing </s:HGroup> tag, add a List component bound to the actions list.
<s:List id="list" dataProvider="{actions}"
top="160" left="0" right="0" bottom="0">
<s:itemRenderer>
<fx:Component>
<components:IconItemRenderer
paddingTop="8" paddingBottom="8" verticalGap="6"
labelField="name"
messageField="details"
decorator="{data.icon}"/>
</fx:Component>
</s:itemRenderer>
</s:List>
5. Run and test the application. When you select an employee in the list, you should see the list of available actions for that employee. The actions don’t work yet. You make them work in the next step.
1. Add a change handler to the List:
<s:List id="list" dataProvider="{actions}"
top="160" left="0" right="0" bottom="0"
change="list_changeHandler(event)">
<s:itemRenderer>
<fx:Component>
<s:IconItemRenderer
paddingTop="8" paddingBottom="8" verticalGap="6"
labelField="name"
messageField="details"
decorator="{data.icon}"/>
</fx:Component>
</s:itemRenderer>
</s:List>
2. Implement list_changeHandler as follows:
protected function list_changeHandler(event:IndexChangeEvent):void
{
var action:Object = list.selectedItem;
switch (action.type)
{
case "tel":
navigateToURL(new URLRequest("tel:"+action.details));
break;
case "sms":
navigateToURL(new URLRequest("sms:"+action.details));
break;
case "mailto":
navigateToURL(new URLRequest("mailto:"+action.details));
break;
}
}
Note: Make sure you import spark.events.IndexChangeEvent (and not mx.events.IndexChangedEvent) for this code to compile:
import spark.events.IndexChangeEvent;
3. Run and test the application
In this section, you make the search feature work. You replace the HTTPService with a RemoteObject that provides a findByName method. For your convenience, the RemoteObject is hosted in the cloud so you don’t have to deploy anything in your own infrastructure.
You could have implemented the search feature using an HTTPService. The reason we are switching to a RemoteObject is to experiment with different data access strategy.
If you are not interested in using a RemoteObject, you can move straight to Part 7.
1. Open EmployeeDirectoryHomeView.mxml. Replace the HTTPService with a RemoteObject defined as follows:
<s:RemoteObject id="srv" destination="employeeService"
endpoint="http://{server}:{port}/{object file path}"
result="data=srv.findByName.lastResult"/>
2. Modify the click handler of the search button: use the RemoteObject’s findByName method to find the employees matching the search key entered by the user.
<s:Button icon="@Embed('assets/search.png')"
click="srv.findByName(key.text)"/>
3. Run and test the application: Type a few characters in the search field and click the search button to see a list of matching employees.
In this section, you change the data access logic of the application: instead of using a RemoteObject (or an HTTPService), you use the SQLite® database available on your device to access the data.
<model:EmployeeDAO id="srv"/>
Note: Make sure the model namespace is bound in the View definition at the top of the mxml document:
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="Home"
xmlns:model="model.*">
<s:Button icon="@Embed('assets/search.png')"
click="data=srv.findByName(key.text)"/>
Note that in this case, we can directly assign the return value of the findByName function to data because EmployeeDAO uses the synchronous version of the database access API.
2. Run and test the application
In this section, you add the “View manager” and “View direct reports” actions to the Employee Details view to allow the user to navigate up and down the org chart.
1. Right-click the views folder in the EmployeeDirectory project and select New > MXML Component.
Specify DirectReports as the component name and click Finish.
2. Implement DirectReports.mxml as follows:
<?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="Direct Reports">
<s:List id="list" top="0" bottom="0" left="0" right="0"
dataProvider="{data.directReports}"
change="navigator.pushView(EmployeeDetails, list.selectedItem)">
<s:itemRenderer>
<fx:Component>
<s:IconItemRenderer
label="{data.firstName} {data.lastName}"
messageField="title"/>
</fx:Component>
</s:itemRenderer>
</s:List>
</s:View>
if (data.manager)
{
actions.addItem({type: "employee", name: "View manager",
details: data.manager.firstName + " " + data.manager.lastName, employee: data.manager});
}
if (data.directReports && data.directReports.length > 0)
{
actions.addItem({type: "reports", name: "View direct reports",
details: "(" + data.directReports.length + ")",
employee: data});
}
2. In the List change handler, add two case statements to trigger the corresponding actions:
case "employee":
navigator.pushView(EmployeeDetails, action.employee);
break;
case "reports":
navigator.pushView(DirectReports, action.employee);
break;
Step 3: Run the application.