Files:
The BBM Profile Box example demonstrates how to create and update the ProfileBox for the application shown in the user's BlackBerry Messenger Profile using the Qt style BBM SP library.
In this example we'll learn how to use the ProfileBox and ProfileBoxItem class of the bb::platform::bbm module to create or delete the application's ProfileBox.
To use the BBM Social Platform, the application must register first. How this is done is explained in bbmregistration and this sample is based on that code. We will describe here everything that needs to be done after the registration was successful.
The business logic of this application is encapsulated in the ProfileBoxManager class, which is exported to QML under the name '_profileBoxManager'.
The main UI of this application is shown after a successful registration. It consists of the main page, which lists the existing ProfileBox items of the application, and a second page which let the user create a new ProfileBox item.
The page contains a ListView as central component, which uses the data model that is provided by the ProfileBoxManager through its 'model' property. This model contains entries that describe the available ProfileBox items. The items are visualized by a custom ListItemComponent, that is implemented in ProfileBoxItem.qml, which displays the icon and label of the item side by side. Whenever the user selects an item, the item's ID is stored in the custom property 'selectedItemId'.
ListView { id: listView property string selectedItemId horizontalAlignment: HorizontalAlignment.Fill verticalAlignment: VerticalAlignment.Fill dataModel: _profileBoxManager.model listItemComponents: [ ListItemComponent { ProfileBoxItem { } } ] onTriggered: { clearSelection() select(indexPath) selectedItemId = dataModel.data(indexPath).id } }
The ID is used by the DeleteActionItem in the action bar to remove the currently selected ProfileBox item from the application's ProfileBox.
actions: [ ActionItem { title: qsTr("New Profile Box") onTriggered: { navigationPane.push(createBoxPage.createObject()) } }, DeleteActionItem { onTriggered: { _profileBoxManager.removeProfileBoxItem(listView.selectedItemId) } } ]
The second ActionItem in the action bar allows the user to open the page for creating new ProfileBox items. The UI is loaded from NewProfileBoxPage.qml via a ComponentDefinition.
attachedObjects: [ ComponentDefinition { id: createBoxPage source: "NewProfileBoxPage.qml" } ]
The UI of the new profile box page basically consists of a TextField to enter the text of the ProfileBox item, a RadioGroup to select the icon of the item and a Button to trigger the actual creation of the ProfileBox item.
TextField { id: profileBoxText hintText: qsTr("Type profile box text here") } RadioGroup { id: profileBoxIcon Option { imageSource: "images/apple.png" value: 1 selected: true } Option { imageSource: "images/pear.png" value: 2 } Option { imageSource: "images/orange.png" value: 3 } }
We call the addProfileBoxItem() method of the ProfileBoxManager and pass the text and the icon ID. The icon ID is a numeric value that must match with the IDs that we register for the ProfileBox (see below).
Button { horizontalAlignment: HorizontalAlignment.Right text: qsTr("Save Profile Box") onClicked: { _profileBoxManager.addProfileBoxItem(profileBoxText.text, profileBoxIcon.selectedValue) navigationPane.pop() } }
The ProfileBoxManager class encapsulates the creation and deletion of the single ProfileBox items. It has a 'model' property of type bb::cascades::DataModel, which makes the available ProfileBox items available to the UI. Furthermore it provides two slots to add and remove a ProfileBox item. The remaining slots and methods are used to update the model or do initialization work.
class ProfileBoxManager : public QObject { Q_OBJECT Q_PROPERTY(bb::cascades::DataModel* model READ model CONSTANT) public: ProfileBoxManager(bb::platform::bbm::Context &context, QObject *parent = 0); public Q_SLOTS: void show(); void addProfileBoxItem(const QString &text, int iconId); void removeProfileBoxItem(const QString &itemId); private Q_SLOTS: void loadProfileBoxes(); void itemAdded(const QString &itemId); void itemRemoved(const QString &itemId); void iconRetrieved(int iconId, bb::platform::bbm::ImageType::Type type, const QByteArray &imageData); private: void registerIcons(); void registerIcon(const QString &path, int iconId); bb::cascades::DataModel* model() const; bb::platform::bbm::ProfileBox* m_profileBox; bb::cascades::QVariantListDataModel* m_model; bb::platform::bbm::Context *m_context; };
Inside the constructor we just initialize the member variables and create the model instance.
ProfileBoxManager::ProfileBoxManager(bb::platform::bbm::Context &context, QObject *parent) : QObject(parent) , m_profileBox(0) , m_context(&context) , m_model(new QVariantListDataModel()) { m_model->setParent(this); }
After a successful registration to the BBM service, the show() slot of the ProfileBoxManager class is invoked. The signal/slot connection for this is established in main.cpp
RegistrationHandler *registrationHandler = new RegistrationHandler(uuid, &app); ProfileBoxManager *profileBoxManager = new ProfileBoxManager(registrationHandler->context(), &app); // Whenever the registration has finished successfully, we continue to the main UI bool ok = QObject::connect(registrationHandler, SIGNAL(registered()), profileBoxManager, SLOT(show())); Q_ASSERT(ok); Q_UNUSED(ok);
Inside the show() slot the UI is loaded from main.qml file and the loadProfileBoxes() slot is invoked in a delayed way.
void ProfileBoxManager::show() { // Create the UI QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this); qml->setContextProperty("_profileBoxManager", this); AbstractPane *root = qml->createRootObject<AbstractPane>(); Application::instance()->setScene(root); // Delay the loading of ProfileBox a bit QMetaObject::invokeMethod(this, "loadProfileBoxes", Qt::QueuedConnection); }
In loadProfileBoxes() the bb::platform::bbm::ProfileBox object is created, which does the low-level communication with the BBM service. We connect our custom slots against the itemAdded() and itemRemoved() signals to get informed whenever new ProfileBox items are added or removed. Additionally we connect against the iconRetrieved() signal to handle the response for icon requests (see below).
Since the ProfileBox does not store the actual image data for the icons but just numeric IDs, we have to register the image data with the icon IDs, which we do in the helper method registerIcons().
Now that everything is prepared, we request all available ProfileBoxItem objects from the ProfileBox object and fill the model with entries that describe them. Instead of the actual icon, we first store the icon ID inside the model entry and trigger a lookup by calling requestRetrieveIcon().
void ProfileBoxManager::loadProfileBoxes() { // Create the profile box object m_profileBox = new bb::platform::bbm::ProfileBox(m_context, this); // Connect all signals to get informed about updates to the profile box bool ok = connect(m_profileBox, SIGNAL(itemAdded(QString)), this, SLOT(itemAdded(QString))); Q_ASSERT(ok); ok = connect(m_profileBox, SIGNAL(itemRemoved(QString)), this, SLOT(itemRemoved(QString))); Q_ASSERT(ok); ok = connect(m_profileBox, SIGNAL(iconRetrieved(int, bb::platform::bbm::ImageType::Type, QByteArray)), this, SLOT(iconRetrieved(int, bb::platform::bbm::ImageType::Type, QByteArray))); Q_ASSERT(ok); registerIcons(); // Fill the model with the initial data foreach (const bb::platform::bbm::ProfileBoxItem &item, m_profileBox->items()) { // Create a model entry const int iconId = item.iconId(); QVariantMap entry; entry["id"] = item.id(); entry["text"] = item.text(); entry["iconId"] = iconId; // Append the entry to the model m_model->append(entry); // Request the icon for this icon ID asynchronously m_profileBox->requestRetrieveIcon(iconId); } }
When the ProfileBox has finished the lookup, it will emit the iconRetrieved() signal, which we bound against the custom iconRetrieved() slot. Inside this slot we convert the raw image data, that are passed as parameter, into a bb::cascades::Image object, so that a ImageView can use it directly. Then we iterate over the entries in the model and add an 'icon' property with the Image object as value if the 'iconId' matches.
void ProfileBoxManager::iconRetrieved(int iconId, bb::platform::bbm::ImageType::Type, const QByteArray &imageData) { const bb::cascades::Image image(imageData); for (int pos = 0; pos < m_model->size(); ++pos) { QVariantMap entry = m_model->value(pos).toMap(); if (entry.value("iconId").toInt() == iconId) { entry["icon"] = QVariant::fromValue(image); m_model->replace(pos, entry); } } }
Whenever some component adds a new ProfileBoxItem to the application's ProfileBox, our custom slot itemAdded() is invoked. Inside there we fetch the new ProfileBoxItem object from the ProfileBox and add a new entry to the model.
void ProfileBoxManager::itemAdded(const QString &itemId) { // Retrieve the new item const bb::platform::bbm::ProfileBoxItem item = m_profileBox->item(itemId); // Create a model entry for it const int iconId = item.iconId(); QVariantMap entry; entry["id"] = itemId; entry["text"] = item.text(); entry["iconId"] = iconId; // Append the entry to the model m_model->append(entry); // Request the icon for this icon ID asynchronously m_profileBox->requestRetrieveIcon(iconId); }
Whenever some component removes a ProfileBoxItem from the application's ProfileBox, our custom slot itemRemoved() is invoked. Inside there we iterate over the entries of the model and remove the one with the matching ID.
void ProfileBoxManager::itemRemoved(const QString &itemId) { // Search the corresponding entry in the model for (int pos = 0; pos < m_model->size(); ++pos) { if (m_model->value(pos).toMap().value("id").toString() == itemId) { // Remove the corresponding entry from the model m_model->removeAt(pos); return; } } }
If the user wants to creates a new ProfileBox item and clicks on the 'Save Profile Box' button in the UI, addProfileBoxItem() is called. This method simply forwards the call to the ProfileBox object.
void ProfileBoxManager::addProfileBoxItem(const QString &text, int iconId) { m_profileBox->requestAddItem(text, iconId); }
If the user wants to delete the currently selected ProfileBox item and triggers the 'Delete' action in the UI, removeProfileBoxItem() is called. This method also forwards the call to the ProfileBox object.
void ProfileBoxManager::removeProfileBoxItem(const QString &itemId) { m_profileBox->requestRemoveItem(itemId); }
The registerIcons() method, which is called in the initialization code above, registers the three icons add the BBM service with the same numeric IDs that we use in the RadioGroup in the UI.
void ProfileBoxManager::registerIcons() { const QString imageDir(QDir::currentPath() + QLatin1String("/app/native/assets/images/")); registerIcon(imageDir + QLatin1String("apple.png"), 1); registerIcon(imageDir + QLatin1String("pear.png"), 2); registerIcon(imageDir + QLatin1String("orange.png"), 3); }
For each icon it calls the helper method registerIcon(), which reads out the raw image data from the file and calls requestRegisterIcon() on the ProfileBox object with the image type and image data as parameters.
void ProfileBoxManager::registerIcon(const QString& path, int iconId) { // Read the icon from the file QFile file(path); if (!file.open(QIODevice::ReadOnly)) return; const QByteArray imageData = file.readAll(); // Create the icon object and register the icon m_profileBox->requestRegisterIcon(iconId, bb::platform::bbm::ImageType::Png, imageData); }