BlackBerry 10 QML Performance Tips [CFA]

Cascades

Here is the second in our series of blog posts from the Cascades Field Agency.  You can check out the first post here (http://devblog.blackberry.com/2013/06/cfa-multi-line-label/) – Erin

TITLE_IMAGE

After looking through the QML code for a couple of applications, I’ve gotten the impression that Cascades application developers have some different habits than those writing QML with the QtQuick elements. While my sample size was pretty small, I think these several points may prove particularly useful to BlackBerry 10 application developers as they write their QML UIs. These tips are focused on enhancing the performance of your QML programs, both in terms of application speed and development/maintenance speed.

Avoid declaring variant properties

The variant type seems pretty useful in QML, and it’s certainly versatile, but don’t over use it. Using variant when you could use a more specific type means that you get less compile-time help and that you pay a performance penalty (to get the real value into and out of a QVariant all the time). If the variable is always a number, use int or real types. If it’s always a string, call it a string.

A common usage of variant is to store controls, but you can use any type exposed QML as the type for your property. For example you can use ‘property Container delayedPart: null’ to hold an item you instantiated dynamically, so long as the root item of the component was a Container or a type based on a Container. Just keep in mind that 0 is a number, not a reference, so use null instead of zero for the initial values of these types. Any type QML knows can be used, not just Container, so there’s no need to use variant for your TextStyleDefinitions or Labels either. There is a bug in Qt 4 where you will still need to use the variant type when passing object references in a QML defined signal handler, but aside from that bug most usages of the variant type can be replaced with a more specific and appropriate type.

Example QML, if you want to delay loading a component with code like:

 subcontrol = myComponentDefinition.createObject();
 replace:
 property variant subcontrol: 0
 with:
 property Container subcontrol: null

Assuming the root item in the component definition is a Container{}. Any object type exposed to QML can be used, not just Container, pick the right one for your component.

Do not use Javascript files just for storing properties

Related to avoiding variant properties, I’ve seen a similar problem with storing properties in JavaScript files. Again this leads to inefficiency in having to wrap and unwrap script values, and again you lose the type information. There’s another problem if you try to alter the properties, as most JavaScript files have a new instance loaded for each QML file that uses them. For better and safer code store these properties in a QML object. This allows for typed properties and makes the instance management a lot clearer and harder to get wrong.

For example, if you previously had a “Constants.js” file like this:

var a = 123;
var b = 4.5;
var c = "6";

Which was used in a file like this:

import "../common/Constants.js" as Constants
Container {
  property int a: Constants.a
  property real b: Constants.b
  property string c: Constants.c
}

You can replace it with a “Constants.qml” like this:

import bb.cascades 1.0
QtObject { //QtObject is the most basic non-visual type
  property int a: 123;
  property real b: 4.5;
  property string c: "6";
}

And use it like this:

import "../common"
Container {
  attachedObjects: [ Constants { id: constants } ]
  property int a: constants.a
  property real b: constants.b
  property string c: constants.c
}

Avoid using .connect

Like the variant type, the connect method on a function is sometimes necessary but not intended to be used a lot. The problem here is not one of performance, but of readability. By connecting signals in this manner you mess up the declarative flow of the code. Normally you should be using the signal handers on the objects themselves instead of adding imperative code into an unrelated signal handler (like creationCompleted). For example you could use

FadeTransition {
  onEnded: otherControl.visible = false
}

instead of

onCreationCompleted: {
  fadeTransitionItem.ended.connect(fadeTransitionEnded);
}
function fadeTransitionEnded() {
  otherControl.visible = false
}

This leads to less and clearer code. Save the connect method for when the object emitting the signal was created in C++ and so cannot be accessed from QML.

Use ComponentDefinition for repeated items

If you need to create many similar items, create them dynamically from a ComponentDefinition instead of copying and pasting the QML code for it. Just add a property int index to the top item in the component, and then add some code like the following:

onCreationCompleted: {
  for (var i = 0; i<n; i++) {
    var item = component.createObject();
    parentContainer.add(item);
    item.index = i;
  }
}

Any slight changes needed between the instances can be based off the index property, and the maintainability is immensely better than if those components had been copied and pasted over and over again.

Use ControlDelegate (or ComponentDefinition) for delayed loading

A good tactic for improving your application start time is to delay loading of any parts of the UI which are not essential for immediate use. It’s easy to do this using ControlDelegate, just wrap the elements you can wait for like so

ControlDelegate {
  id: controlDelegate
  delegateActive: false
  sourceComponent: ComponentDefinition {
    Container { id: thisIsYourControl }
  }
}

And after the essential elements are loaded, set delegateActive to true. Once your control is loaded, it will function the same as before.

Let the UI flow through QML

Cascades is not purely QML APIs and it’s quite easy to drop down to C++ if you want to. Often this is a good way to do expensive logic faster. Normally though you should only be moving business logic to C++, not UI logic. For UI logic it’s usually better to keep it all in QML for two reasons. The first is that it’s easier to develop and maintain, as the entire UI in both appearance and flow is defined in one place. You should architect your application so that the UI flow is controlled at a high level from QML. If you need a complex C++ controller class or initialization logic, expose that to QML and control it from QML (by calling a function or by just creating it) to perform the complex logic, instead of having it managed in C++ and emit a signal to QML when it’s done. This places the high level UI flow just in QML, making it a lot easier to follow and manage, despite complex logic still being performed in C++. A common method of doing this is to expose a single QObject to the root context, containing all the application global methods and properties, which are then invoked, queried, or bound to from QML.

Example Code, from C++:

qmlDocument->setContextProperty("app", myControllerInstance);

In QML:

onClicked: {
  showLoadingScreen();
  app.fetchData(app.defaultDataSource);
  app.dataReady.connect(hideLoadingScreen());
}

Note that because the app object is exposed from C++, you have to use the connect function to hook up the signal.

The second reason to keep your UI in QML is that you can actually lose some performance benefits when passing messages back and forth from the QML file to your C++ class. If you just need to handle some touch events in a way that doesn’t have a pre-made gesture you can store a couple of state variables inside the QML file instead of creating a C++ controller class. It saves you both development and run-time costs of hooking up the touch events from QML into the special object, and it keeps all the application touch handling code in one place.

Use your own URI

I’ve seen some code that exposes its C++ types like this:

qmlRegisterType<MyType>("bb.cascades", 1, 0, "MyType");

Do not do this. Features are being added to the QML language to prevent you from injecting types into existing imports, meaning that this code will break in the future. But more importantly, this is avoiding the whole point of the module import system which leads to a lot of cases where your code can break even sooner! It also makes the code easier to understand and debug if proper imports are used. This can be as simple as:

qmlRegisterType<MyType>("App", 1, 0, "MyType");

And then import App 1.0 in your QML file.

CFA comment: Even though using your own namespace is correct, the fact is that you will currently break the momentics preview if you use your own URI. We recommend that during development you use bb.cascades and once you are done with the UI you change it to your own namespace.

So there’s a few tips for writing faster QML with Cascades. Faster to write, faster to run, and faster to maintain. All worthy goals.

About Erin Rahnenfuehrer

I'm living the dream, working for the Developer Relations team at BlackBerry speciailizing in Cascades and C++ development. I focus mostly on social and lifestyle apps.

Join the conversation

Show comments Hide comments
+ -
blog comments powered by Disqus