Updating QML live on a target

You can quickly test changes to your UI by using the DevelopmentSupport class. This class sends QML updates from the Momentics IDE for BlackBerry to your BlackBerry 10 device without redeploying your app.

The QML updates that you make in the IDE are sent to your running app where you can instantly view the results. This streamlined update feature makes developing a QML-based UI much faster. If you have code that can't be rendered easily in QML Preview, such as animations or custom controls, you can test changes to these controls with the DevelopmentSupport class. This feature is also useful for testing an app that uses data models that QML Preview can't render.

To use the DevelopmentSupport class, you must be using Momentics IDE version 2.0 or later and you must have an API level of 10.2 or later. For more information about API levels, see API levels.

Animation showing the use of the DevelopmentSupport class to see changes to QML code on a target. If you missed it, click the image to play it again.

Did you blink? Click the image above to see it again!

Set up development support in the IDE

To use development support in the IDE, you must turn on the Cascades Development Support option.

  1. In Windows and Linux, on the Window menu, click Preferences. In Mac OS, on the Momentics menu, click Preferences.
  2. Click BlackBerry.
  3. Select Send QML files to device on save.
  4. Click Apply.
  5. Click OK.
Screen showing the Cascades Development Support option in Preferences in the Momentics IDE.

Add development support 10.2

In API level 10.2 or 10.3, you need to add a little code to your project to set up your app to receive the updates from the Momentics IDE. The following template can be added to any project. You can download the DevelopmentSupport template from GitHub or you can follow along below.

Create a QmlBeam custom component

First, create a class in the src folder of your project using File > New > Other > C/C++ > Class. Type QmlBeam in the Class name field. In the QmlBeam.h file, add the following code:

#ifndef QMLBEAM_H_
#define QMLBEAM_H_

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

namespace bb
{
    namespace cascades
    {
        class AbstractPane;
    }
}
/**
 * QmlBeam Description
 *
 * A utility object that can be used to run the application using
 * the DevelopmentSupport signals to update the QML part of your app
 * on the fly. Every time you save a QML file, the file is uploaded
 * to the device and the application scene is reloaded. 
 */
class QmlBeam: public QObject
{
    Q_OBJECT

public:
    QmlBeam(QObject *parent = 0);

private slots:

     // The reloadQML() function is where the QML gets 
     // reloaded in the application.

    void reloadQML(QUrl mainFile);

     // The cleanup() function is where the saved  
     // application context is cleaned up.

    void cleanup();

private:
    // Root pane of the application scene
    bb::cascades::AbstractPane* mRoot;
};

#endif /* QMLBEAM_H_ */

The QmlBeam custom component implements two slots. When QML assets are changed and saved in the Momentics IDE, the assetsChanged() signal is emitted. The reloadQML() slot is connected to the assetsChanged() signal and responds by refreshing the QML scene graph. All updated QML files in the application assets folder are sent to the device. The cleanup() slot is connected to the aboutToQuit() signal and deletes the private variable mRoot that represents the scene graph of the app.

Next, implement the QmlBeam class in the QmlBeam.cpp file:

#include "QmlBeam.h"

#include <QDeclarativeContext>

#include <bb/cascades/AbstractPane>
#include <bb/cascades/core/developmentsupport.h>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

QmlBeam::QmlBeam(QObject *parent) : QObject(parent)
{
    bool connectResult;
    Q_UNUSED(connectResult);

    // Get the application scene. It's needed since
    // the scene is recreated when QML files are changed.
    mRoot = Application::instance()->scene();
    Q_ASSERT(mRoot);

    mRoot->setParent(this);

    // Create the Development support object and connect to
    // its signal, so that the application scene can be recreated
    // when changes are made to the QML files.
    DevelopmentSupport* devSupport = new DevelopmentSupport(this);
    connectResult = connect(devSupport,
                            SIGNAL(assetsChanged(QUrl)),
                            SLOT(reloadQML(QUrl)));
    Q_ASSERT(connectResult);


    connectResult = connect(Application::instance(),
                            SIGNAL(aboutToQuit()),
                            SLOT(cleanup()));

    Q_ASSERT(connectResult);
}

void QmlBeam::reloadQML(QUrl mainFile) {
    // Get the context of the first scene root 
    // to keep the contextProperties
    QDeclarativeContext* context = 
        QDeclarativeEngine::contextForObject(mRoot);

    // Clear the QML cache
    QDeclarativeEngine* appEngine = context->engine();
    Q_ASSERT(appEngine);
    appEngine->clearComponentCache();

    // Reload all QML
    QmlDocument* qml = QmlDocument::create(mainFile);
    AbstractPane *root = 
            qml->createRootObject<AbstractPane>(context);
    qml->setParent(root);
    Application::instance()->setScene(root);
}

void QmlBeam::cleanup() {
    // Clean up.
    Application::instance()->setScene(0);
    mRoot->setParent(0);
    delete mRoot;
}

This implementation of QmlBeam assumes that your UI is implemented in a main.qml file.

Add QmlBeam to your app

Now, you need to include the QmlBeam class in the constructor of your app and create an instance of the QmlBeam object after you set the scene for your UI using setScene().

Since the QmlBeam object was set up as a class, you can reuse this template in all of your existing projects. In the following sample, the QmlBeam object is added to the applicationui.cpp file in a standard empty project template, but you can add it to any existing project. In your src folder, find the .cpp file that sets the main scene for your app and add the #include "QmlBeam.h" statement and create a QmlBeam object:

#include "applicationui.hpp"
#include "QmlBeam.h"

// ... 

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

    // ...

    // Set created root object as the application scene
    app->setScene(root);
    
    new QmlBeam(this);
}

Add development support 10.3.1

In API level 10.3.1, you can call the DevelopmentSupport::install() function to add development support to your application code. For example, if you create a project using a standard empty template, you can add the following code to your main.cpp file:

#include <bb/cascades/DevelopmentSupport>

// ...

DevelopmentSupport::install();

Here's an example of a main.cpp file with development support configured:

#include "applicationui.hpp"

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

#include <QLocale>
#include <QTranslator>

#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

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

    // Enable development support.
    DevelopmentSupport::install();

    // Create the Application UI object; this is where the 
    // main.qml file is loaded and the application scene is set.
    ApplicationUI appui;

    // Enter the application main event loop.
    return Application::exec();
}

Try it out

Now you can test your setup.

  1. Deploy your app to your target device.
  2. When the app is running on the device, change the main.qml file in your project and save your changes in the IDE.
  3. Watch the app reload the UI on your device. Every time you save a change to the main.qml file, the file is uploaded to the device and the application scene is reloaded. You can change existing controls to your UI or add new controls to your UI.

When you release your app, make sure that the DevelopmentSupport class and related code aren't included in the executable.

Limitations

There are some limitations to be aware of:

  • Only changes to QML files are recognized. Changes to image assets or XML files are not updated in the running app on the device.
  • Changes to application C++ code in your app can't be updated while the app is running.
  • New QML files are not detected on the device. If you add a QML file to your project, you must redeploy your app.
  • There is no way to know which files have changed when the assetsChanged() signal is emitted. Reloading the full scene from the main.qml file ensures that all changes appear on the device. If you want to watch for changes to specific files, you must create your own signal and signal handler.
  • Currently, the development support feature is only supported on device targets, not simulator targets.

Related resources

Last modified: 2014-09-30



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

comments powered by Disqus