The BlackBerry Bramble

BlackBerry 10 Developer Blog

Loading QML Conditionally

My multiFEED application was originally built at a time when most users were still running the BlackBerry 10.0 operating system. Even as I write this, there are still quite a few sluggish carriers around the world that haven’t issued an update to 10.1, leaving their customers locked out from using some of the more interesting apps that have become available since the BlackBerry 10 launch at the end of January 2013. multiFEED allows extensive user configuration, and some of those settings required somewhat awkward combinations of checkboxes, dropdowns, and sliders. I decided that some of them would work much better as “custom pickers“. This style of control looks much like the wheels of a mechanical slot machine, and has been used in a few BlackBerry 10 native controls, such as DateTimePicker, since the advent of BlackBerry 10. I was halfway through happily converting some of my settings to use custom pickers when I realized they were introduced with BlackBerry 10.1 and were not available on 10.0.

Now, it is important to me not to alienate or isolate my existing users that are still stuck on 10.0 by changing the minimum required OS version to 10.1.0, so I knew I needed a solution that would give 10.1 users access to the new custom pickers but continue using the old style sliders and dropdowns if the user was running an older OS version. Since I am fluent with C++ my initial idea was to move the control definitions out of QML and into C++ classes. The plan was that I would check the OS version, then build the controls on the fly to use only the control types that were available on that platform. It seemed like a workable solution, but I discovered very quickly that it didn’t work. The instant your C++ code includes a reference to the Picker class, the application won’t start up, even if that class is never instantiated. What I needed was a way to get the app to start up successfully, and only then compile the source code to decide which types of control to instantiate. The answer? Conditional QML loading.

If you have developed your application entirely with QML you may not realize that all the QML you write is only turned into machine code at runtime. All BlackBerry Cascades apps start up some C++ code, which instantiates a class to manage the application, and which in-turn reads main.qml and all other QML source files that it refers to. If your QML code contains errors, it is the C++ code in your application class that reports the errors and shuts the application down when you try to start it.

In a QML-only application this is the only place where the QML code is read and converted to machine instructions, but you can load QML from C++ code anywhere in your application you like.

The Basic Code

Let’s build a basic C++ class that can decide which QML to read at runtime. Here is the header file:

Our new class inherits from CustomControl so it can be placed on a page just like any standard QML control type. We need the Q_OBJECT macro declaration so we can make our smart control available to the rest of the QML in the application. Note that we have declared a Container* variable called root. The type of this class is important and must match the outermost control type in the QML we will be loading. If the QML was going to be a Sheet definition, then this declaration would be a Sheet* instead. Now for the basic cpp file:

Lines 10-14 are used to retrieve the OS version and extract the major and minor version portions. Lines 16-22 use this information to load the correct QML source file for that OS version. If all goes well then lines 27 and 28 get a reference to the outermost Container in the loaded QML and set it as the root for our smart control. MyPickerOld.qml and MyPickerNew.qml are standard QML source files, and will look something like this:

The only other thing we need to do now is make our smart control available to the other application QML files. In main.cpp at the beginning of the main() function, declare the following:

… and then in any QML file that you wish to use your smart picker control in add this at the top of the file:

 Getting Stuff In and Out

If your MyPickerOld.qml and MyPickerNew.qml are completely self-contained and you don’t need to get any values in or out that aren’t standard CustomControl properties or respond to any of their custom signals outside of their own internal signal handlers, then you are done. Just design the two versions of your QML and the appropriate one will be loaded depending on the OS version. In most cases though you will need access to custom properties as well as handle custom signals.

Let’s start by adding a custom property to both of our QML source files:

You might think that since what the user sees on their screen is one of the Containers you have designed that you can just access this property directly like any other control. Unfortunately this won’t work because the control on the screen is actually not a Container, it is a CustomControl. The Container the user is seeing is actually held in the private root variable on MyPicker. To access the new property we need to duplicate it on the CustomControl and add read and write handlers for it to the hpp file:

…and the cpp file:

All the handler functions do is pass the requests on down to the Container property. Now you can reference myIntProperty on the parent page and you will be get or set the value on the actual QML control. Setting the QML version of the property this way will trigger the onMyIntPropertyChanged signal on the Container, so your custom QML control will know and can respond if myIntProperty is changed from the parent page.

Handling Signals

Unfortunately we are not quite done yet, since the signal sent when myIntProperty on the QML control is changed won’t propagate up to the CustomControl for use on the parent page. Once again our CustomControl must act as intermediary. To do this we must declare a matching signal on MyPicker:

…and then connect the QML signal to it:

Now you can handle changes to myIntProperty on the parent page like usual:

Improving Performance

If you frequently access myIntProperty from the parent page the overhead of passing the request down to the QML each time might slow things down a bit too much. If so, consider keeping a local copy of the value on MyPicker and return that value instead. To do this, create a new private int variable to hold the current value and a new slot on MyPicker to handle the change signal from the QML:

…then connect the QML change signal to it instead of directly to the MyPicker signal:

…and finally create the slot function and change the get handler:

Putting It All Together

Now lets combine everything. First create two OS version specific QML files:

Next we need a header file to define the MyPicker class:

And finally the body of the MyPicker class:

Testing

While you are developing the two versions of the custom control QML file you can use the latest version of the simulator to write them both, but I strongly suggest you test everything before release with device simulators for the OS versions your QML is designed to handle, or an actual device running those versions to ensure that you don’t inadvertently include any code that won’t run on the older operating system.

Conclusion

BlackBerry continues to improve their flagship mobile OS by adding new features and controls, but if you want to use these improvements without forfeiting the ability of your application to run on older versions of BlackBerry 10 you need a way to load QML on-the-fly depending on the OS version the app finds itself running on. Even if your whole app was developed entirely in QML and you know only the bare minimum C++ to make your app run, you should be able to copy-paste the code here and tweak it for your own needs.

,

Leave a Reply