Tutorial: Creating dynamic covers

The previous sections demonstrated how to create app covers using static images and controls. In the previous examples, the content of the covers doesn't change. This tutorial shows you how to create dynamic covers.

Dynamic covers are useful because they can provide the user with specific information regardless of the current app context. For example, a calendar app might show details about the next event.

The image on the right shows an app cover that contains dynamic content. The content of the cover contains information about recent updates from a user's friends.

You will learn to:

  • Create content for a dynamic cover
  • Use C++ to retrieve components from the UI and set their properties
  • Use a Q_INVOKABLE function called update() from main.qml whenever updates need to occur
  • Set the content for the cover using the setContent() function
  • Set the cover on the app
Screen showing the Active Frame from BBM.

Before you begin

This tutorial assumes that you're familiar with the fundamentals of Cascades and the Momentics IDE for BlackBerry, and that you've built and run several Cascades apps already. If you're just starting out and need to know some of the basics, you might want to check out a few of these resources before you start this tutorial:

Viewing the full source code

This tutorial uses a step-by-step approach to show you how to build dynamic covers.The complete source code is provided at the end of this tutorial.

Creating a dynamic cover

Open the ActiveFrameQML.cpp file and have a look at the source code. Creating the content for a dynamic cover isn't different from creating content for a static cover. You create the scene as you normally would, using C++ or QML, but now you can update the content.

A typical way to create a dynamic cover is to create a class that inherits from SceneCover. In this example, the class is called ActiveFrameQML. The content for the cover is defined in a separate QML file (AppCover.qml). In the constructor, findChild() is called on the root container to retrieve the Label component that is updated. The class also contains a Q_INVOKABLE function called update(), which is called from main.qml whenever updates must occur. The content for the cover is set using the setContent() function.

Here's the implementation of the ActiveFrameQML class:

#include "ActiveFrameQML.h"
#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

ActiveFrameQML::ActiveFrameQML(QObject *parent)
	: SceneCover(parent)
{
	QmlDocument *qml = QmlDocument::create("asset:///AppCover.qml")
            .parent(parent);
	Container *mainContainer = qml->createRootObject<Container>();
	setContent(mainContainer);

    // Retrieves the label from QML that we want to update
	m_coverLabel = mainContainer->findChild<Label*>("TheLabel");
	m_coverLabel->setParent(mainContainer);
}

void ActiveFrameQML::update(QString appText) {

	m_coverLabel->setText(appText);
}

Setting the cover on the app

To set the cover on app, you create an instance of ActiveFrameQML and set it on the Application in the ApplicationUI constructor (for example, applicationui.cpp). You must also expose the ActiveFrameQML instance as a context object to the main.qml file, because that's where updates to the cover are initiated from.

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
		QObject(app) {

	QmlDocument *qml = 
        QmlDocument::create("asset:///main.qml").parent(this);

	ActiveFrameQML *activeFrame = new ActiveFrameQML();
	Application::instance()->setCover(activeFrame);

	qml->setContextProperty("activeFrame", activeFrame);

	AbstractPane *root = qml->createRootObject<AbstractPane>();
	app->setScene(root);
}

Updating the cover

To begin updating the cover, your app must listen for the thumbnail() signal, which is emitted every time the app moves to the background. This example shows how to connect a slot to the thumbnail() signal from within main.qml. Connecting to the signal from QML is partly for convenience, because the data that's used to update the cover is also located within this signal. Depending on your app, you might want to connect to the thumbnail() signal from C++ instead.

Whenever the thumbnail() signal is emitted, the update() function is called. The update() function passes the text property from the TextField to the ActiveFrameQML instance, which is then used to update the Label on the cover.

import bb.cascades 1.0

Page {
    Container {
        TextField {
            id: appLabel
            text: "Hello World"
            textStyle.base: SystemDefaults.TextStyles.BigText
        }
    }
    onCreationCompleted: {
        Application.thumbnail.connect(onThumbnail)
    }
    function onThumbnail() {
        activeFrame.update(appLabel.text);
    }
}

Complete source code for this example

#include "applicationui.hpp"
#include <bb/cascades/Application>
#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);

    new ApplicationUI(&app);

    return Application::exec();
}
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

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

using namespace ::bb::cascades;

class ApplicationUI : public QObject
{
    Q_OBJECT

public:
    ApplicationUI(bb::cascades::Application *app);
    virtual ~ApplicationUI() { }
};

#endif /* ApplicationUI_HPP_ */
#include "applicationui.hpp"
#include "ActiveFrameQML.h"
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>

using namespace bb::cascades;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
		QObject(app) {

	QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

	ActiveFrameQML *activeFrame = new ActiveFrameQML();
	Application::instance()->setCover(activeFrame);

	qml->setContextProperty("activeFrame", activeFrame);

	AbstractPane *root = qml->createRootObject<AbstractPane>();
	app->setScene(root);
}
#ifndef ACTIVEFRAMEQML_H_
#define ACTIVEFRAMEQML_H_

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

using namespace ::bb::cascades;

class ActiveFrameQML: public SceneCover {
	Q_OBJECT

public:
	ActiveFrameQML(QObject *parent=0);

public slots:
	Q_INVOKABLE void update(QString appText);

private:
	bb::cascades::Label *m_coverLabel;
};

#endif /* ACTIVEFRAMEQML_H_ */
#include "ActiveFrameQML.h"
#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

ActiveFrameQML::ActiveFrameQML(QObject *parent)
	: SceneCover(parent)
{
	QmlDocument *qml = QmlDocument::create("asset:///AppCover.qml")
            .parent(parent);
	Container *mainContainer = qml->createRootObject<Container>();
	setContent(mainContainer);

	m_coverLabel = mainContainer->findChild<Label*>("TheLabel");
	m_coverLabel->setParent(mainContainer);
}

void ActiveFrameQML::update(QString appText) {

	m_coverLabel->setText(appText);
}
import bb.cascades 1.0

Page {
    Container {
        TextField {
            id: appLabel
            text: "Hello World"
            textStyle.base: SystemDefaults.TextStyles.BigText
        }
    }
    onCreationCompleted: {
        Application.thumbnail.connect(onThumbnail)  
    }
    function onThumbnail() {
        activeFrame.update(appLabel.text);
    }
}
import bb.cascades 1.0

Container {    
    Container {        
        layout: DockLayout {}
        background: Color.Black
        
        ImageView {
            imageSource: "asset:///images/app-cover.png"
            scalingMethod: ScalingMethod.AspectFit
        }
        
        Container {
            bottomPadding: 31
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Bottom
            
            Container {
                preferredWidth: 300
                preferredHeight: 42
                background: Color.create("#121212")
                layout: DockLayout {}
                
                Label {
                    objectName: "TheLabel"
                    horizontalAlignment: HorizontalAlignment.Center
                    verticalAlignment: VerticalAlignment.Center
                    text: "QML"
                    textStyle.color: Color.create("#ebebeb")
                    textStyle.fontSize: FontSize.PointValue
                    textStyle.fontSizeValue: 6
                }
            }
        }
    }
}
Screen showing the image file used in the app cover example.

Using Active Frames efficiently

When you create dynamic covers, you should consider how often you update the covers, and how it might affect performance and battery life. For more information about creating covers efficiently, see Update Active Frames efficiently.

Last modified: 2015-03-31



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

comments powered by Disqus