Getting Started
This chapter will show you how to set up your BlackBerry 10 development environment and deploy your first application on the BlackBerry 10 simulator and on a physical device. You will also get a broad perspective of the Cascades programming model, as well as its most essential features. In setting up your environment, I will walk you through the following steps:
BlackBerry 10 is a major mobile operating system overhaul. It’s the third release built on top of the extremely reliable QNX operating system, which is used in critical applications ranging from medical devices to nuclear power plants. QNX is also POSIX compliant, meaning that if you’re familiar with a UNIX programming API, you will feel just at home with the operating system’s calls. Another big advantage of building BlackBerry 10 on top of a POSIX system is the availability of a myriad of open-source libraries that you can include in your own projects.
A key feature of BlackBerry 10 is that it is built using a multilayered architecture where QNX is the backbone providing essential services such as multithreading, memory management, and security, to name a few (see Figure 1-1). The layer on top of QNX includes the BlackBerry Platform Services (BPS) as well as several modules from the Qt framework.
Figure 1-1. BlackBerry 10 platform
BPS is an API written in C, giving low-level access to the BlackBerry 10 device. It’s mostly used when you need to write high-performance applications such as games that require the most effective way of accessing the hardware. BPS is not the main subject of this book. I will nevertheless give you examples of how to use it, but I will mostly concentrate on the higher-level modules built on top of BPS.
Qt is a C++ framework providing an abstraction layer to the lower-level POSIX APIs. It also adds many classes and components essential to C++ programming. The following modules from the Qt framework have been ported to the BlackBerry 10 platform and can be used in your own applications:
The Qt modules mostly provide non-GUI functionality for your application. To build rich native applications with an engaging UI, you need to rely on the Cascades layer of the BlackBerry 10 architecture. In fact, Cascades is much more than a GUI framework; it also includes the following nonexhaustive list of services and APIs:
When developing native applications, you will notice that there is some overlap between the functionality provided by Cascades and the underlying modules. At first this might seem confusing but you should keep in mind that Cascades often provides a richer and easier-to-use API. Therefore, as a good rule of thumb, always try to implement a functionality with the Cascades API first, and if it is not possible, use the underlying Qt or BPS modules. Networking is a good example where you will use the QtNetwork module essentially.
When building user interfaces with Cascades, you can proceed in two distinct ways: you can either write imperative code in C++ or create your UI declaratively with the Qt Modeling Language (QML). Most examples in this book use the latter approach for the following reasons:
To illustrate the previous points, let’s design a very simple UI using both approaches: one UI design in QML and another one in C++. As shown in Figure 1-2, the UI isn’t very fancy; it’s simply a text field stacked on top of a slider. Whenever the slider moves, the text field is updated with the slider’s new position.
Figure 1-2. Stacked TextField and Slider
Listing 1-1 shows the QML markup version.
Listing 1-1. main.qml
import bb.cascades 1.0
Page {
Container {
TextField {
id: texfield
}
Slider{
id: slider
fromValue: 0
toValue: 100
onImmediateValueChanged: {
texfield.text = Math.round(immediateValue)
}
}
}
}
The equivalent C++ version of the code for creating the same UI is given in Listings 1-2 and 1-3.
Don’t worry if you have never programmed in C++, we will cover the basics in Chapter 3. As a matter of fact, you will also see in Chapter 2 that you can build relatively complex Cascades applications using QML/JavaScript only, without ever writing a single line of C++ code.
Listing 1-2. applicationui.hpp
class ApplicationUI : public QObject
{
Q_OBJECT
public:
ApplicationUI(bb::cascades::Application *app);
virtual ∼ApplicationUI() { }
public slots:
void onImmediateValueChanged(float value);
};
Listing 1-3. applicationui.cpp
ApplicationUI::ApplicationUI(bb::cascades::Application *app) : QObject(app) {
Page *page = new Page();
Container *contentContainer = new Container();
contentContainer->setLayout(StackLayout::create());
TextField* textfield = TextField::create();
Textfield->setObjectName("textfield");
Slider* slider = Slider::create();
slider->setFromValue(0);
slider->setToValue(100);
contentContainer->add(textfield);
contentContainer->add(slider);
QObject::connect(slider, SIGNAL(immediateValueChanged(float)), this,
SLOT(onImmediateValueChanged (float)));
page->setContent(contentContainer);
app->setScene(page);
}
void ApplicationUI::onImmediateValueChanged(float value) {
value = round(value);
QString stringValue = QString::number(value);
Application* app = static_cast<Application*>(this->parent());
TextField* textField = app->scene()->findChild<TextField*>("textfield");
textField->setText(stringValue);
}
ApplicationUI is the “application delegate” in charge of creating the user interface and wiring together the application’s controls’ event handling. You have to provide this class and it is instantiated during the application bootstrap process.
As you can see, the declarative way of building the UI in QML is very concise compared to the imperative C++ approach. This is also because Cascades takes care of a lot of the plumbing work for you behind the scenes when you’re using QML.
Signals and Slots
In Cascades terminology, event handling is done using signals and slots, which are basically a loosely coupled notification mechanism between controls. Whenever something interesting happens to a control, such as a state change, a predefined signal is emitted for notifying that change. If you’re interested in receiving that notification, then you have to specify some application logic in JavaScript or C++, which will be called in the corresponding Cascades predefined signal handler. Signals and slots are part of the QtCore module. The Cascades framework uses them in order to build a high-level event handling mechanism. This section will expand on the topic in order to give you a firm grip on the way signals and slots work. As noted previously, the most important property of signals is their ability to let you bind objects together without them knowing about each other.
For a given predefined signal signal, Cascades also provides a corresponding predefined onSignal handler (which is also called equivalently a slot). You can write JavaScript code in your QML document to tell Cascades what to do when the handler is triggered and how the control should respond to the signal. For example, in order to handle the slider’s position updates, Cascades defines a predefined onImmediateValueChanged signal handler called when the slider emits the immediateValueChanged signal. In Listing 1-1, the predefined handler will execute the texfield.text = Math.round(immediateValue) JavaScript code in order to update the textfield. You will also notice that the JavaScript code references an immediateValue parameter. Signals usually include extra parameters that provide additional information about them. In QML, they are implicitly available to the JavaScript execution context and you can use them in order to retrieve further information about the change that just occurred.
You can refer to the Cascades API reference found at http://developer.blackberry.com/cascades/reference/user_interface.html for a list of all predefined signals and corresponding slots organized by GUI control. Look under the core controls section.
Looking at Listing 1-2, you will notice that I’ve used the slots: annotation to declare an onImmediateValueChanged(float value) slot in the application delegate class. In Listing 1-3, I’ve connected the slider’s onImmediateValueChanged(float value) to the application delegate’s onImmediateValueChanged(float value) slot using the QObject::connect(source, SIGNAL(signal), destination, SLOT(slot)) method.
The Q_OBJECT, signals: and slots: “annotations” are Qt extensions to the C++ language.
Signals and slots are implemented in Qt using the following constructs:
If you intend on extending the class, you must also repeat the Q_OBJECT macro in all of its subclasses.
As an example, let us consider the case of a temperature sensor. We would like to build a system where we can chart and log temperature readings over time. We would also want to decouple the system by separating the charting logic from the temperature logging. A very simplified design can be implemented using three classes (see Figure 1-3). The TempSensor class is responsible for the temperature readings through the setTemp(float newValue) function, which could be triggered by a hardware interrupt. The function would then update TempSensor’s internal state, and then emit a tempChanged(float) signal. The TempChart and TempLogger classes would respectively handle the signal with a corresponding onTempChanged(float) slot.
Figure 1-3. Sensor system
The C++ implementation is given in Listings 1-4 and 1-5.
Listing 1-4. TempSensor.hpp
#include <QObject>
class TempSensor : public QObject{
Q_OBJECT
public:
TempSensor(QObject* parent=0) : QObject(parent), m_value(0) {};
virtual ∼TempSensor(){};
void setTemp(float newValue){
if(m_value == newValue) return;
m_value = newValue;
emit(tempChanged(m_value);
}
signals:
void tempChanged(float)
private:
float m_value;
};
#include <QObject>
class TempChart : public QObject{
Q_OBJECT
public:
TempChart(QObject* parent=0) : QObject(parent){};
public slots:
void onTempChanged(float value){
// do charting
}
};
#include <QObject>
class TempLogger : public QObject{
Q_OBJECT
public:
TempLogger(QObject* parent=0) : QObject(parent){};
public slots:
void onTempChanger(float value){
// do logging
}
};
Listing 1-5. main.cpp
#include "TempSensor.hpp"
int main(){
TempSensor sensor;
TempLogger logger;
TempChart chart;
QObject::connect(sensor, SIGNAL(tempChanged(float)), logger, SLOT(onTempChanged(float)));
QObject::connect(sensor, SIGNAL(tempChanged(float)), chart, SLOT(onTempChanged(float)));
// do temperature readings here.
}
Here are a few things to keep in mind when implementing signals and slots:
Qt extends C++ with a meta-object system in order to introduce runtime introspection features that would not be available with a statically compiled language such as C++. Behind the scenes, Qt uses the meta-object compiler (MOC) to generate the extra C++ plumbing code for the functions declared by the Q_OBJECT macro and for the class signals. Finally, the QObject::connect function uses the MOC-generated introspection functions to wire signals and slots together. When building Cascades applications, the MOC is called transparently by the build system.
Cascades Application Bootstrap Process
The entry point for all Cascades applications is the main function shown in Listing 1-6.
Listing 1-6. main.cpp
#include <bb/cascades/Application>
#include <QLocale>
#include <QTranslator>
#include "applicationui.hpp"
#include <Qt/qdeclarativedebug.h>
using namespace bb::cascades;
Q_DECL_EXPORT int main(int argc, char **argv)
{
Application app(argc, argv);
// Create the Application UI object, this is where the main.qml file
// is loaded and the application scene is set.
new ApplicationUI(&app);
// Enter the application main event loop.
return Application::exec();
}
The first step in main is to create an instance of a bb::cascades::Application class, which provides the application’s run loop, and all the boilerplate functionality required by a Cascades application. At this point, you will have a “bare bones” Cascades app but the run loop has not kicked in yet. To further customize the application, the following properties of the bb::Cascades::Application instance have to be specified:
In practice, you will not update the bb::cascades::Application’s properties directly in the main function but instead rely on an application delegate object, which will take care of loading or creating the main scene and wiring all the events using signals and slots. You’ve already seen an implementation of an application delegate in Listing 1-2 and Listing 1-3 given by the ApplicationUI class. In Listing 1-3, we customized the application delegate in order to build the scene graph using C++. Listing 1-7 shows the default version generated by the Momentics IDE’s New BlackBerry Project wizard (more on installing your development environment later in the chapter).
Listing 1-7. applicationui.cpp
ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
QObject(app)
{
// prepare the localization. Code omitted
// Create scene document from main.qml asset, the parent is set
// to ensure the document gets destroyed properly at shut down.
QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
// Create root object for the UI
AbstractPane *root = qml->createRootObject<AbstractPane>();
// Set created root object as the application scene
app->setScene(root);
}
I’ve removed the code related to localization in order to concentrate on the scene graph creation logic. Here an instance of a bb::cascades::QmlDocument is created by reading the main.qml QML file containing the declarative UI description. This is the same QML you will design using the Cascades Builder tool.
Finally, once the application delegate has been initialized, the application’s main event loop kicks in through a call to bb::cascades::Application::exec().
If you take a close look at Listing 1-3, you will notice that I haven’t released the objects allocated with the new operator at any point in the code. This might seem as a memory leak but it’s not. Cascades widgets are organized in a parent-child relationship that also handles object ownership and memory management. In the case shown in Listing 1-3, the root parent of the entire object hierarchy is the bb::cascades::Application app object. The memory associated with the child controls will be released when this object is deleted by the runtime. I will cover memory management in detail in Chapter 3, but for the moment you can safely assume that there are no memory leaks in Listing 1-3.
To build Cascades applications, you need to set up the native SDK using the following steps:
Figure 1-4. BlackBerry keys order form
Figure 1-5. BlackBerry ID token
To create Cascades applications, you will use the Momentics IDE, which essentially adds extra plug-ins and tools to a standard Eclipse distribution (if you’ve already used Eclipse in the past for Java or Android development, you will be right at home; otherwise, don’t worry—this section will guide you through the IDE). This section explains how a Cascades project is organized in Momentics and reviews the most important features of the IDE that you will be using frequently. First start by creating a new Cascades project using the following steps:
Figure 1-6. BlackBerry 10 Platform
Momentics stores your projects in a workspace, which is essentially a collection of projects located in the same directory on your file system. Once you’ve finished creating the HelloCascades project, your workspace should look similar to Figure 1-7.
Figure 1-7. Momentics workspace
A perspective is a task-oriented collection of views and editors. When designing Cascades applications, you will mostly use the QML Editing, C/C++, and Debug perspectives. You can easily switch from one perspective to another using the perspectives toolbar or the Window Open Perspective navigation menu. Some views, such as the Project Explorer, will appear in multiple perspectives.
In the Project Explorer view, the src subfolder contains the following C++ source files:
You’ve already seen simplified versions of these files in the examples in Listing 1-7. For the moment, you can simply ignore them. The assets subfolder contains the main.qml defining your application’s UI.
Let’s spice up the default version of the app generated by the Cascades wizard.
The source code for this book is located in the https://github.com/aludin/BB10Apress GitHub repository and at www.apress.com/9781430261575. You can either clone the repository or download a compressed Zip copy. As you read along, you can import the projects in turn in Momentics (in Momentics, select File Import Existing Projects into Workspace and select the root directory of a project located under the BB10Apress folder).
Figure 1-8. Momentics IDE, QML perspective
main.qml should now look like Listing 1-8.
Listing 1-8. main.qml
import bb.cascades 1.0
Page {
Container {
//Todo: fill me with QML
Label {
id: helloCascades
// Localized text with the dynamic translation and locale updates support
textStyle.base: SystemDefaults.TextStyles.BigText
text: "Hello Cascades"
horizontalAlignment: HorizontalAlignment.Center
}
}
}
onImmediateValueChanged: {
swissalpsnight.opacity = immediateValue
}
The final version of the QML markup should look like Listing 1-9. If not, try to repeat the previous steps until you reach the same result, or simply update the QML directly in the Source view.
Listing 1-9. main.qml
import bb.cascades 1.0
Page {
Container {
//Todo: fill me with QML
Label {
id: helloCascades
// Localized text with the dynamic translation and locale updates support
textStyle.base: SystemDefaults.TextStyles.BigText
text: "Hello Cascades"
horizontalAlignment: HorizontalAlignment.Center
}
Container {
id: imageContainer
horizontalAlignment: HorizontalAlignment.Center
layout: DockLayout {
}
ImageView {
id: swissalpsday
imageSource: "asset:///images/swissalpsday.png"
}
ImageView {
id: swissalpsnight
imageSource: "asset:///images/swissalpsnight.png"
}
}
Slider {
horizontalAlignment: HorizontalAlignment.Center
onImmediateValueChanged: {
swissalpsnight.opacity = immediateValue
}
}
}
}
Congratulations! You’ve just finished designing your first Cascades application!
Build Configurations
There are four build configurations to consider when creating Cascades application:
A build configuration defines a set of rules and settings for building your application for a given processor or target (for example, the “Simulator debug” configuration will build your project with debug symbols enabled for a Simulator target, whereas “Device release” will build a release version of your project for a physical device with an ARM processor). At any point, you can set the active build configuration, as explained in the following paragraph.
To build the project for the simulator, select HelloCascades in Project Explorer, and then set Project Build Configurations Set Active Simulator-Debug from the Momentics main menu. Next, select Project Build Project. The build starts immediately and the build output is displayed in the Console View.
When the build finishes, a new folder called x86/o-g containing the build results will be created under your project’s root folder.
Note that another extremely convenient way of selecting a build configuration is by using the BlackBerry Toolbar, as shown in Figure 1-9 (you will also see in the next section how to use the BlackBerry Toolbar to set up targets). To build the project, select Debug for the build type and then click the Hammer button.
Figure 1-9. BlackBerry Toolbar
Before testing HelloCascades, you need to define a deployment target. On the BlackBerry Toolbar, select the Manage Devices… option located in the Active Device drop-down (this will display the Device Manager wizard; see Figure 1-10 and Figure 1-11).
Figure 1-10. Manage devices
Figure 1-11. Device Manager (figure also shows installed simulators)
Simulator
To configure a new simulator using the Device Manager wizard, follow these steps:
Figure 1-12. Simulator versions
Figure 1-13. Simulator pairing
You can now try to launch HelloCascades on the simulator using the green Debug button on the BlackBerry Toolbar (if you haven’t built the project previously, click the Hammer button; see Figure 1-9).
Device
Configuring a new physical device for testing purposes is accomplished by pairing the device with Momentics. You will also have to generate a debug token, which will be saved on the device by Momentics. Once again, the BlackBerry Toolbar streamlines the process:
Figure 1-14. Set up new BlackBerry 10 device
Figure 1-15. Pair device using USB
Figure 1-16. Create Debug Token
This time, you can try to launch HelloCascades on the device by selecting it as the Active Device on the BlackBerry Toolbar.
The purpose of this section is to explain what’s happening behind the scenes when you use the BlackBerry Toolbar, which essentially creates launch configurations for you. A launch configuration is purely an Eclipse concept and not at all specific to Momentics; it associates a build result with a target. You must create it in order to run your application on a simulator or a device. There are two kinds of launch configurations that you can create: the Run Configuration and the Debug Configuration. In this section, I will show you how to create a Debug Configuration for the Simulator target. (The steps for creating a Run Configuration are identical to a Debug Configuration. A Run Configuration will simply launch your application on the target, whereas a Debug Configuration will launch it under Momentics’ debugger control.)
Figure 1-17. Debug Configurations
Figure 1-18. Simulator launch configuration
Figure 1-19. Debug perspective
Figure 1-20. Hello Cascades on the simulator
To create a debug Launch configuration for the device, you basically need to repeat the same steps, with the following differences:
Once again, launch configurations can be completely ignored by using the BlackBerry Toolbar, but it is always a good idea to have a basic understanding of their functionality.
API Levels
An API level is a set of APIs and libraries that your application builds against. It also corresponds to a version of the BlackBerry 10 OS. API levels are backward compatible. (Higher API levels include APIs from the previous releases, although some APIs could be deprecated. In other words, this is identical to the way Java manages its APIs.) If for some reason you need to compile against a specific API level, you can change the setting in Momentics using Momentics Preferences BlackBerry API Level.
QNX System Information Perspective
Before finishing this chapter, I want mention the Momentics QNX System Information perspective, which can be used for navigating your device’s or simulator’s filesystem (you can open the perspective by selecting Windows Open Perspective QNX System Information; see Figure 1-21). As you develop Cascades applications, you will realize that the possibility to access your device will be extremely useful for retrieving logs from the target file system or for monitoring your application’s memory and CPU usage.
Figure 1-21. QNX System Information perspective
Summary
This chapter gave you a bird’s-eye view of the BlackBerry 10 platform and the Cascades programming model. I showed you how to declaratively design your UI using QML, which is much more efficient than using imperative C++ code. QML is therefore the preferred approach—not just because the Cascades framework takes care of a lot of the plumbing work for you, but also because you can rely on the Cascades Builder tools to visually design your UI. You can nevertheless still rely on C++, something that we will further discuss in Chapter 3, for the performance-critical aspects of your application. Signals and slots were introduced as a high-level mechanism used by Cascades for event handling and I explained how to use them in your own code for reacting to events generated by UI controls.
You discovered how the Momentics IDE was organized in Perspectives, giving a task-centric view of your work. The three most important ones are the QML Editing, C/C++ Editing, and Debug perspectives. You will be using them time and again when creating Cascades applications. We also went through the configuration of a BlackBerry device for development purposes, as well as the generation of the debug tokens required for application deployment on a device. Finally, you learned how to create, build, and launch configurations for your application in order to deploy it on a simulator or device.