Tutorial: Using BPS

This tutorial shows you how to use several platform services in a Cascades app using BlackBerry Platform Services (BPS). We create an app that displays the current locale and network connection status on a device. The app uses APIs in the BPS library to retrieve locale and network status events, and the app updates the information accordingly if these values change.

You will learn to:

  • Create a custom handler class for BPS events
  • Request events from different platform services
  • Handle events that are received through BPS and update Cascades controls in response
Screen showing the result of the tutorial.

Downloading the full source code

This tutorial uses a step-by-step approach to build our BPS app from scratch. If you want to look at the complete source code for the finished app, you can download the entire project and import it into the Momentics IDE for BlackBerry. To learn how, see Importing and exporting projects.

Download the full source code

Create the UI

To start, open the Momentics IDE and create a Cascades project using the Standard empty project template. To make it easier to follow along, the code in this tutorial assumes that you give your project a name of BpsTutorial. After the project is created, open the main.qml file, which is where we put the QML code for the UI of our app. Go ahead and remove the prepopulated code that's included in the file; we'll start coding from a blank file.

We create several Label controls, each of which displays a different piece of information that we'll retrieve using BPS functions later on. The Label controls are centered on the screen using a DockLayout. We make sure to give each Label an objectName, which lets us populate the values from C++.

import bb.cascades 1.4

Page {
    Container {
        layout: DockLayout {
        }
        Container {
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            Label {
                id: countryLabel
                objectName: "countryLabel"
            }
            Label {
                id: languageLabel
                objectName: "languageLabel"
            }
            Label {
                id: localeLabel
                objectName: "localeLabel"
            }
            Label {
                id: networkStatusLabel
                objectName: "networkStatusLabel"
            }
            Label {
                id: networkStatusType
                objectName: "networkStatusType"
            }
        }
    }
}
import bb.cascades 1.4

Page {
    Container {
        layout: DockLayout {
        }
        Container {
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            Label {
                id: countryLabel
                objectName: "countryLabel"
            }
            Label {
                id: languageLabel
                objectName: "languageLabel"
            }
            Label {
                id: localeLabel
                objectName: "localeLabel"
            }
            Label {
                id: networkStatusLabel
                objectName: "networkStatusLabel"
            }
            Label {
                id: networkStatusType
                objectName: "networkStatusType"
            }
        }
    }
}

Define the app class

Next, open the applicationui.cpp file. This file represents our app class and is located in the src folder of our project.

This class sets the appropriate text for each Label, while another custom class called StatusEventHandler takes care of the BPS operations that our app uses. We'll create the StatusEventHandler class a bit later.

In applicationui.cpp, we can keep the prepopulated code that's provided. In the app constructor, we add code that locates each Label that we created in QML (by using their objectName properties) and assigns the objects to C++ variables. We also call a function called initLabels(), which sets the initial text of each Label. The constructor should look something like this:

ApplicationUI::ApplicationUI() :
        QObject()
{
    // Template code to handle localization and translation
    // ...

    // 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>();
    
    // Find the labels and assign them to variables
    countryLabel = root->findChild<Label*>("countryLabel");
    languageLabel = root->findChild<Label*>("languageLabel");
    localeLabel = root->findChild<Label*>("localeLabel");
    networkStatusLabel = root->findChild<Label*>("networkStatusLabel");
    networkStatusType = root->findChild<Label*>("networkStatusType");

    // Set created root object as the application scene
    Application::instance()->setScene(root);
    
    // Initialize the text of each label
    initLabels();
}

Let's define the initLabels() function now. In addition to setting the text of each Label, this function creates an instance of StatusEventHandler, which we'll use to interact with the BPS library. The functions in this class emit a couple of custom signals, networkStatusUpdated() and localeUpdated(), when either the network status or locale changes. We connect these signals to slot functions in our app class so that the text of each Label can be updated appropriately.

void ApplicationUI::initLabels()
{
    countryLabel->setText("Country: ?");
    languageLabel->setText("Language: ?");
    localeLabel->setText("Locale: ?");
    networkStatusLabel->setText("Network Status: ?");

    statusEventHandler = new StatusEventHandler();
    
    // If any Q_ASSERT statement(s) indicate that the slot failed to
    // connect to the signal, make sure you know exactly why this has
    // happened. This is not normal, and will cause your app to
    // stop working!!
    bool connectResult;
    
    // Since the variable is not used in the app, this line is added
    // to avoid a compiler warning.
    Q_UNUSED(connectResult);
    
    connectResult = connect(statusEventHandler,
                    SIGNAL(networkStatusUpdated(bool, QString)),
                    this,
                    SLOT(networkStatusUpdateHandler(bool, QString)));
            
    // Q_ASSERT is available only in Debug builds.
    Q_ASSERT(connectResult);
            
    connectResult = connect(statusEventHandler,
                    SIGNAL(localeUpdated(QString, QString, QString)),
                    this,
                    SLOT(localeUpdateHandler(QString, QString, QString)));
    
    // Q_ASSERT is available only in Debug builds.
    Q_ASSERT(connectResult);
}

To complete our app class definition, we define our two slot functions, networkStatusUpdateHandler() and localeUpdateHandler(). These functions are straightforward: they update the Label controls with the new network status and locale information (which the function parameters provide).

void ApplicationUI::networkStatusUpdateHandler(bool status, QString type)
{
    if (status) {
        networkStatusLabel->setText("Network Status: true");
    } else {
        networkStatusLabel->setText("Network Status: false");
    }
    networkStatusType->setText("Network Type: " + type);
}

void ApplicationUI::localeUpdateHandler(QString language, QString country, QString locale)
{
    countryLabel->setText("Country: " + country);
    languageLabel->setText("Language: " + language);
    localeLabel->setText("Locale: " + locale);
}
#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/LocaleHandler>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);

    bool res = QObject::connect(m_pLocaleHandler, SIGNAL(systemLanguageChanged()), this, SLOT(onSystemLanguageChanged()));
    // Q_ASSERT is available only in Debug builds
    Q_ASSERT(res);
    // Since the variable is not used in the app, this line is added to avoid a
    // compiler warning
    Q_UNUSED(res);

    // initial load
    onSystemLanguageChanged();

    // 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>();

    // Find the labels and assign them to variables
    countryLabel = root->findChild<Label*>("countryLabel");
    languageLabel = root->findChild<Label*>("languageLabel");
    localeLabel = root->findChild<Label*>("localeLabel");
    networkStatusLabel = root->findChild<Label*>("networkStatusLabel");
    networkStatusType = root->findChild<Label*>("networkStatusType");

    // Set created root object as the application scene
    Application::instance()->setScene(root);

    // Initialize the text of each label
    initLabels();
}

void ApplicationUI::initLabels()
{
    countryLabel->setText("Country: ?");
    languageLabel->setText("Language: ?");
    localeLabel->setText("Locale: ?");
    networkStatusLabel->setText("Network Status: ?");

    statusEventHandler = new StatusEventHandler();

    // If any Q_ASSERT statement(s) indicate that the slot failed to
    // connect to the signal, make sure you know exactly why this has
    // happened. This is not normal, and will cause your app to
    // stop working!!
    bool connectResult;

    // Since the variable is not used in the app, this line is added to
    // avoid a compiler warning.
    Q_UNUSED(connectResult);

    connectResult = connect(statusEventHandler,
                    SIGNAL(networkStatusUpdated(bool, QString)),
                    this,
                    SLOT(networkStatusUpdateHandler(bool, QString)));

    // Q_ASSERT is available only in Debug builds.
    Q_ASSERT(connectResult);

    connectResult = connect(statusEventHandler,
                    SIGNAL(localeUpdated(QString, QString, QString)),
                    this,
                    SLOT(localeUpdateHandler(QString, QString, QString)));

    // Q_ASSERT is available only in Debug builds.
    Q_ASSERT(connectResult);
}

void ApplicationUI::networkStatusUpdateHandler(bool status, QString type)
{
    if (status) {
        networkStatusLabel->setText("Network Status: true");
    } else {
        networkStatusLabel->setText("Network Status: false");
    }
    networkStatusType->setText("Network Type: " + type);
}

void ApplicationUI::localeUpdateHandler(QString language, QString country, QString locale)
{
    countryLabel->setText("Country: " + country);
    languageLabel->setText("Language: " + language);
    localeLabel->setText("Locale: " + locale);
}

void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name = QString("BpsTutorial_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->installTranslator(m_pTranslator);
    }
}

Update the app header file

Now, let's update the corresponding header file to reflect the changes we've made to the source file. In the src folder of your project, open the applicationui.hpp file.

We need to include a few additional header files, so we add the appropriate include statements at the top of the file (right below #include <QObject>):

#include <bb/cascades/Label>

#include "StatusEventHandler.h"

In a public slots: section of the header file, we declare our two slot functions. Add the public slots: section if it doesn't exist already:

public slots:
    void localeUpdateHandler(QString language, QString country, QString locale);
    void networkStatusUpdateHandler(bool status, QString type);

Finally, we declare our private variables (the Label controls), our StatusEventHandler instance, and the initLabels() function in the private: section of the header file:

private:
    bb::cascades::Label *countryLabel;
    bb::cascades::Label *languageLabel;
    bb::cascades::Label *localeLabel;
    bb::cascades::Label *networkStatusLabel;
    bb::cascades::Label *networkStatusType;
    StatusEventHandler  *statusEventHandler;
    void initLabels();
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>
#include <bb/cascades/Label>

#include "StatusEventHandler.h"

namespace bb
{
    namespace cascades
    {
        class LocaleHandler;
    }
}

class QTranslator;

/*!
 * @brief Application UI object
 *
 * Use this object to create and init app UI, to create
 * context objects, to register the new meta types etc.
 */
class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
public slots:
    void localeUpdateHandler(QString language, QString country, QString locale);
    void networkStatusUpdateHandler(bool status, QString type);
private slots:
    void onSystemLanguageChanged();
private:
    bb::cascades::Label *countryLabel;
    bb::cascades::Label *languageLabel;
    bb::cascades::Label *localeLabel;
    bb::cascades::Label *networkStatusLabel;
    bb::cascades::Label *networkStatusType;
    StatusEventHandler  *statusEventHandler;
    void initLabels();
    QTranslator* m_pTranslator;
    bb::cascades::LocaleHandler* m_pLocaleHandler;
};

#endif /* ApplicationUI_HPP_ */

Create the event handler class

Most of our app's real work is done in a custom class called StatusEventHandler. Go ahead and create a class with this name in the src folder of your project (right-click the src folder and click New > Class). You can use the default options to create the StatusEventHandler class, but make sure to clear the Namespace check box if it's selected.

Open the StatusEventHandler.cpp file. We start by including the appropriate BPS header files for the platform services that our app uses (namely, network status and locale), right below the include statement for our StatusEventHandler.h header file:

#include <bps/bps.h>
#include <bps/netstatus.h>
#include <bps/locale.h>

In the class constructor, we call the subscribe() function, which registers our app to receive BPS events from the platform services that we're interested in. These function calls are required because our custom class, StatusEventHandler, inherits from a special class called AbstractBpsEventHandler. You'll learn why we chose to use this class a bit later in the tutorial.

We initialize the BPS library by calling bps_initialize(), which lets us use BPS functions in our app. We want our app to receive events related to network status and locale, so we call the netstatus_request_events() and locale_request_events() functions to request events from these services.

StatusEventHandler::StatusEventHandler() {
    subscribe(netstatus_get_domain());
    subscribe(locale_get_domain());

    bps_initialize();

    netstatus_request_events(0);
    locale_request_events(0);
}

Remember that when our app is finished using the BPS library, we need to call bps_shutdown() to correctly free the resources that were allocated. We can use the destructor in StatusEventHandler for this:

StatusEventHandler::~StatusEventHandler() {
    bps_shutdown();
}

Handle BPS events

The key to handling BPS events lies in the next function that we implement, the event() function. As you'll see when we complete the corresponding header file, our StatusEventHandler class inherits not only from QObject (as most Cascades classes do) but also from AbstractBpsEventHandler. This abstract class lets us implement the event() function, which is called when an event arrives in the BPS event queue. In our constructor, we subscribed to both network status and locale events (by calling subscribe()), so these types of events are passed to event() when they arrive in the queue.

By subclassing AbstractBpsEventHandler, we don't need to implement an event loop to continuously check for and process new events. Relevant events are delivered to our event() function as they arrive, so we can handle them then instead of polling for new events. This approach makes our app more efficient.

We start our event() function implementation by creating variables to store the network status and locale information that we'll retrieve from BPS events:

void StatusEventHandler::event(bps_event_t *event) {
    bool status = false;
    const char* language = "";
    const char* country = "";
    const char* locale = "";
    const char* interface = "";
    const char* type = "none";

When we receive an event, we need to know which platform service generated that event. Each event has a domain that corresponds to the service that generated it, and we can use the bps_event_get_domain() function to retrieve this domain. We need to determine whether the event is a network status event:

if (bps_event_get_domain(event) == netstatus_get_domain()) {

In addition to a domain, each event also has a code that identifies the specific type of event that was generated. We're interested in network information events, so we test for those types of events:

if (NETSTATUS_INFO == bps_event_get_code(event)) {

The network status service gives us access to a lot of information about the active network connection on the device. For example, we can retrieve the status of the network connection (active or not active):

netstatus_info_t *info = netstatus_event_get_info(event);
if (info) 
{
    status = netstatus_info_get_availability(info);

We can also determine whether the device is connected using a wired, Wi-Fi, or Bluetooth connection. We retrieve the default interface used by the device and retrieve the detailed information that's associated with this interface:

interface = netstatus_info_get_default_interface(info);

netstatus_interface_details_t *details;

int success = netstatus_get_interface_details(interface, &details);

If we're able to retrieve the interface details successfully, we determine what specific type of interface is being used for the network connection and store that information:

if (success == BPS_SUCCESS) {
    switch (netstatus_interface_get_type(details)) {

    case NETSTATUS_INTERFACE_TYPE_UNKNOWN:
        type = "Unknown";
        break;

    case NETSTATUS_INTERFACE_TYPE_WIRED:
        type = "Wired";
        break;

    case NETSTATUS_INTERFACE_TYPE_WIFI:
        type = "Wi-Fi";
        break;

    case NETSTATUS_INTERFACE_TYPE_BLUETOOTH_DUN:
        type = "Bluetooth";
        break;

    case NETSTATUS_INTERFACE_TYPE_USB:
        type = "USB";
        break;

    case NETSTATUS_INTERFACE_TYPE_VPN:
        type = "VPN";
        break;

    case NETSTATUS_INTERFACE_TYPE_BB:
        type = "BB";
        break;

    case NETSTATUS_INTERFACE_TYPE_CELLULAR:
        type = "Cellular";
        break;

    case NETSTATUS_INTERFACE_TYPE_P2P:
        type = "P2P";
        break;
    }
}

After we're finished with the interface details, we need to free the memory that was allocated for the details structure, so we call netstatus_free_interface_details() to accomplish this. To indicate that the network status information has been updated, we emit a custom signal called networkStatusUpdated(). Earlier in the tutorial, we connected this signal to a slot function, networkStatusUpdateHandler(), in our applicationui.cpp file. When we emit the signal, the slot function is called and the text of each Label is updated appropriately.

    netstatus_free_interface_details(&details);
    emit networkStatusUpdated(status, type);
}

Now that we've handled network status events, we can test to determine whether the event is a locale event. If so, we make sure that the event is a locale information event and update the language, country, and locale values appropriately. The locale service includes functions that retrieve each of these values from an event. Finally, we emit the custom signal localeUpdated() to indicate that the locale information has changed and that our app's UI should be updated to reflect the change.

    } else if (bps_event_get_domain(event) == locale_get_domain()) {
        if (LOCALE_INFO == bps_event_get_code(event)) {
            language = locale_event_get_language(event);
            country = locale_event_get_country(event);
            locale = locale_event_get_locale(event);
            emit localeUpdated(language, country, locale);
        }
    }
}
#include "StatusEventHandler.h"

#include <bps/bps.h>
#include <bps/netstatus.h>
#include <bps/locale.h>

StatusEventHandler::StatusEventHandler()
{
    subscribe(netstatus_get_domain());
    subscribe(locale_get_domain());

    bps_initialize();

    netstatus_request_events(0);
    locale_request_events(0);
}

StatusEventHandler::~StatusEventHandler()
{
    bps_shutdown();
}

void StatusEventHandler::event(bps_event_t *event) {
    bool status = false;
    const char* language = "";
    const char* country = "";
    const char* locale = "";
    const char* interface = "";
    const char* type = "none";

    if (bps_event_get_domain(event) == netstatus_get_domain()) {
        if (NETSTATUS_INFO == bps_event_get_code(event)) {
            netstatus_info_t *info = netstatus_event_get_info(event);
            if (info)
            {
                status = netstatus_info_get_availability(info);
                interface = netstatus_info_get_default_interface(info);

                netstatus_interface_details_t *details;

                int success = netstatus_get_interface_details(interface, &details);
                if (success == BPS_SUCCESS) {
                    switch (netstatus_interface_get_type(details)) {

                    case NETSTATUS_INTERFACE_TYPE_UNKNOWN:
                        type = "Unknown";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_WIRED:
                        type = "Wired";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_WIFI:
                        type = "Wi-Fi";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_BLUETOOTH_DUN:
                        type = "Bluetooth";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_USB:
                        type = "USB";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_VPN:
                        type = "VPN";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_BB:
                        type = "BB";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_CELLULAR:
                        type = "Cellular";
                        break;

                    case NETSTATUS_INTERFACE_TYPE_P2P:
                        type = "P2P";
                        break;
                    }
                }

                netstatus_free_interface_details(&details);
                emit networkStatusUpdated(status, type);
            }
        }
    } else if (bps_event_get_domain(event) == locale_get_domain()) {
        if (LOCALE_INFO == bps_event_get_code(event)) {
            language = locale_event_get_language(event);
            country = locale_event_get_country(event);
            locale = locale_event_get_locale(event);
            emit localeUpdated(language, country, locale);
        }
    }
}

Update the handler header file

Our app is almost complete. We just need to update the header file for our handler class, StatusEventHandler.h. Open this file and add the following include statements:

#include <QObject>
#include <QString>
#include <bb/AbstractBpsEventHandler>

Remember that our class inherits from both QObject and AbstractBpsEventHandler, so we update the class signature to reflect this. Every QObject subclass also needs to include the Q_OBJECT macro, which allows the class to use Qt features such as signals and slots.

class StatusEventHandler : public QObject, public bb::AbstractBpsEventHandler {
        Q_OBJECT

When you create a class that inherits from QObject and any other classes (in our example, AbstractBpsEventHandler), QObject must appear first in the list of inherited classes.

We declare our constructor, destructor, and the event() function, as well as our two custom signals:

public:
    StatusEventHandler();
    virtual ~StatusEventHandler();
    virtual void event(bps_event_t *event);

signals:
    void networkStatusUpdated(bool status, QString type);
    void localeUpdated(QString language, QString country, QString locale);
};
#ifndef STATUSEVENTHANDLER_H_
#define STATUSEVENTHANDLER_H_

#include <QObject>
#include <QString>
#include <bb/AbstractBpsEventHandler>

class StatusEventHandler : public QObject, public bb::AbstractBpsEventHandler {
        Q_OBJECT
public:
    StatusEventHandler();
    virtual ~StatusEventHandler();
    virtual void event(bps_event_t *event);

signals:
    void networkStatusUpdated(bool status, QString type);
    void localeUpdated(QString language, QString country, QString locale);
};

#endif /* STATUSEVENTHANDLER_H_ */

Include the appropriate library

Our final step is to include the bb library in our project. This library includes AbstractBpsEventHandler, so we need to add a line to the .pro file in our project to be able to use this class. Open the BpsTutorial.pro file and add the following line immediately after the CONFIG line:

LIBS += -lbb

We're done! Build and run the app to see the results.

Last modified: 2015-01-15



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus