Revisiting the BlackBerry 10 NDK

published at 28.08.2013 18:06 by Jens Weller
Save to Instapaper Pocket

The last few weeks I did look again at the BlackBerry 10 NDK, as a client had asked for help and training. I offered to adapt my Qt Introduction course to the BlackBerry plattform, and offered my advice and expierence from my introduction series for BB10, which I published earlier this year on YouTube. So, I'd like to share my thoughts and expierences of revisiting the BlackBerry 10 NDK with you. I already did blog my first impressions about the BB10 NDK in Spring.

Before I start, a short paragraph about Apps and C++. People coming from Java or .net often don't understand the need to make Apps in C++. Especially coming from a world constrained to OO and GC it is not easy to understand all concepts used in C++. In my opinion it makes a lot of sense to use C++ for app developement, especially in connection with such a powerful framework as Qt. One of the reasons is performance, as C++ is really close to the hardware, your app runs on as less power as possible. Also, there are some scaling issues in perfomance and hardware in the future, as Herb Sutter pointed out in the freelunch is over. With Qt moving also to Android and iOS, C++ and Qt/QML become a really powerful combination to create apps.

So, when building apps for BlackBerry 10, you need to start with the BlackBerry NDK. I haven't had the time to play around with the new 1.2 Version, but the IDE seems to be a lot better and stable. But as 1.2 hasn't yet reached gold status, targeting 1.1 is in my opinion currently the best, except you need some of the new features coming with 1.2. The BlackBerry NDK comes with the Cascades Framework, which is the API you should build your app with for BB10. Cascades is build on top of Qt and uses QML. While Qt5 has QtQuick 2, BlackBerry has there own implementation of QML, running in a rendering thread. So QML written with QtQuick1 or 2, won't run under Cascades. Also Qt5 isn't yet fully supported by Cascades, currently Cascades is based on Qt 4.8.

Since 2010 I've been looking at Qt mobile and QML to create apps, was involved with MeeGo, and now BlackBerry. QML in BB10 is a bit different, as it is composed out of UI Elements, such as Container, Pages or Controls, while QtQuick1/2 also offer very basic elements such as Item or Rectangle. So for QML and its API BlackBerry is its own little world. While its Qt5 applications are possible to build and run under BB10, this will not give you the full integration as otherwise Cascades would.

Documentation and talks about this have mostly focused on using QML for most things, and using/connecting C++ where it seems helpful. For example writing the models in C++ and exposing members of a class via Q_PROPERTY macros to QML. Most work is then done in QML. QML offers a few ways to check for errors such as console.assert, but as QML translates into Javascript it lacks any kind of strong typesation or checking for correctness. If you misspell a variable, this can lead to QML not noticing your error, and simply interpreting this as a new variable. A simple example:

Container {
    layout: DockLayout {
    }
    Label{
        text:ListItemData.titel
    }
}

This is a simple QML Element to be displayed in a ListView, ListItemData lets you access the data to display. I've made a little error, actually the element is title, but titel is the german word for this. So, a german person, won't notice this at first. And for QML, well, it won't notice either. You could insert any word there, QML isn't checked in compiletime for this, and neither does this give you an error at runtime. It simply will not display anything in the text, and maybe print a message to console. But you'll have to setup the console correctly to see this in the IDE if you work on the device.

How to solve this? Behind Cascades is a C++ Framework build with Qt, so at least in C++ we have the chance to detect this, and log an error. Unfortunately, its not possible to make this a compiletime error, I'm thinking about tweaking it in this direction, but for now, no way. So, Q_ASSERT at runtime has to do it. For all Cascades Elements used in QML there is a C++ class as counterpart, which is at runtime created for each element. The Cascades API lets you look up those instances, and gives you a little more control on certain things. For ListView there is also a class which provides the Items for such a ListView from C++: ListItemProvider. This class has a simple interface to use:

	virtual bb::cascades::VisualNode* createItem(bb::cascades::ListView* listview,const QString& type);
	virtual void updateItem(bb::cascades::ListView* listview,bb::cascades::VisualNode* node,const QString& type,const QVariantList& indexPath, const QVariant& data);

Overwriting those virtual methods allows for creating Items displayed in a ListView, and also updating them with actual data. BlackBerry does provide Examples on how to implement this. Unfortunately, those examples don't use QML, they use the full C++ as an approach. But I'd like to use QML for UI. Also, this OO Style implies deriving a ListItemProvider for every ListView where you want to use this. Well, I tried to solve this once and for all, so that there is a ListItemProvider for general purpose use. As this is runtime dependend, templates are not an option, lets have a look at the actual implementation, before I get to createItem a short stop at addType, a helper method to add handlers for types:

void ListViewItemProvider::addType(const QString& type, const QString& qmlasset, const listitem_callback& callback)
{
    bb::cascades::QmlDocument* doc = bb::cascades::QmlDocument::create(qmlasset);
    if(!doc->hasErrors())
    {
doc->setParent(this); type_map.insert(type,doc); callback_map.insert(type,callback); }//TODO add additional error checking & handling }

This method adds different handlers for different types. Those types are described by QML as QStrings, so a QMap<QString, QmlDocument*> saves the supported types. This method turns the asset into a QmlDocument via create. As a little sitenode, create actually does not return a QmlDocument* as the above code suggests. It returns the internal Builder class for building QmlDocuments as a reference, then seems to kick in a conversion into QmlDocument*. As QmlDocument is derived from QObject, I register the factoryclass as the parent, so that it gets cleaned up later. So, not a lot to see here, next lets see what happens in createItem:

bb::cascades::VisualNode* ListViewItemProvider::createItem(bb::cascades::ListView* listview,const QString& type)
{
     if(type_map.find(type)!=type_map.end())
     {
          bb::cascades::Container* node = type_map[type]->createRootObject<bb::cascades::Container>();
          return node;
     }
     Q_ASSERT_X(false,__FUNCTION__,type +" TYPE not handled");
     bb::cascades::Container* con = new bb::cascades::Container(0);
     bb::cascades::Label* label = new bb::cascades::Label(con);
     label->setText("ERROR");
     return con;
}

The code tests if the type is registered, and then creates the item. This uses QmlDocument::createRootObject, which returns a pointer to the created root object. This is a template method, so we have to know the type of the root object to create it. For now I have decided to use the rule that all UI Elements, which are used in such a way have Container as the root element. Maybe VisualNode*, the return type, would also be a valid Type here. The error case is much more interesting. What to do? The Q_ASSERT_X kicks in and reports the error. But if this happens in a release build, this won't do, also still the method needs to return a value. Returning 0 would be a valid option, but the documentation isn't mentioning 0 as a possible return value. But it states that the returned pointer will be owned by the ListView. Even if I can return 0 (hopefully BB10 devs check for this...), this would hide the error from a possible tester. So I decided to return a little Container with a Label stating Error. Maybe I could replace it with a better message. But in this way, also a tester will see the error. Throwing an exception would also be possible, but as after this the control flow returns again to the Cascades API and Qt, thats not the best option. As Qt and Cascades don't use exceptions, but BB10 offers support for exceptions.

The last thing to implement is updateItem, which is also the only thing which actually contains code that isn't that easily to write in a generic way. After all, the loaded QML file needs to be loaded with the correct data, which is also one of the reasons to start this effort. But there is an option to get the implementation for this out of this class: registering a callback, that is then called for the corresponding type. So updateItem only does this callback invocation:

if(callback_map.find(type)!=callback_map.end())
{
#ifndef USE_BOOST
		(*callback_map[type])(node,indexPath,data);
#else
		callback_map[type](node,indexPath,data);
#endif
}

Until now, I could hide the USE_BOOST define, but for such a callback, a C++ Programmer should first think about boost::function. And as BlackBerry states that boost is one of the supported open source libraries, I of course used it. Turns out, that is not that easy, at least my build chain under Linux runs into an error in boost/type_traits/detail/cv_trait_impl.hpp. I know that boost is used by others, so maybe this is only a linux or buildchain issue. The error seems to be coming from the preprocessor, who fails on correctly testing for GCC <= 3.2, while the actual version is 4.6.3, a strange error from the preprocessor occurs, stating that braces mismatch. I choose to patch my local version of boost for this, and reported the issue to boost and BlackBerry. If you use boost on BB10, you should use the boost version from BlackBerry on Github. As not everybody likes to deal with boost, I also created a non boost version, this is also to have working version in case the patch wouldn't work for some reasons later.

Last but not least, lets see how to implement the callback:

void ApplicationUI::callbackMyListItem(bb::cascades::VisualNode* node,const QVariantList& indexPath, const QVariant& data)
{
    bb::cascades::ImageView* image = node->findChild<bb::cascades::ImageView*>("imageview");
Q_ASSERT(image); if(image) { QString name_image = "image";//this must be correct! QVariantMap map = data.toMap(); bool hasdata = map.contains(name_image); Q_ASSERT(hasdata); if(hasdata) image->setImageSource(map[name_image].toUrl()); } }

In this case, the path to an image is set. The VisualNode Pointer is derived from QObject, so that the child can be queryied. As findChild might return 0 in case it didn't find the Objectname it was looking for. As this shouldn't be the case a Q_ASSERT tests for this case. Then the datalookup in the QVariantMap happens. As there should always be an image, the map is tested if it contains such an element. If not the assert will kick in again. The callback is simply registered with boost::bind.

The datalookup can also happen via your datamodel, BB10 does not support for normal models from Qt, but rather choose to implement their own modelclasses. Which is often fine, but personally like the models from Qt a bit more, also you can reuse them later when going to build an App with Qt for Android, iOS, the Desktop or even Jolla. KDAB, one of our Gold Sponsors for this years conference has published a solution, that bridges this gap, and makes Qt models usable in Cascades.

A few words on the IDE, as I said earlier, the IDE has improved with 1.2. As the IDE is getting better, it is in some cases still far away from being good. The QML Editor is still not as good as it should be, but if it crashes won't crash your IDE now. Alternative could be QtCreator, as the support for QML has also improved there. For now, I think the Momentics IDE from BlackBerry is better then the QtCreator for Cascades. First, there is no Cascades integration in QtCreator for the QML, so autocompletion won't work, this is because a certain file is missing in the NDK, to specify the types. For this reason also the visual editor won't work for QML. Qt is of course a little better supported in QtCreator, but the 1.2 version of the NDK improves here a lot. The project templates which the QtCreator offers are not as good as the ones Momentics creates, they lack the translator code for example. I like that Momentics Templates include the QTranslator code in the main.cpp. Both the Momentics and QtCreator could create and run an app on my DevAlpha, so development in QtCreator for BB10 is possible, but there is room for improvement.

There is a few more things I'd like to share, but I currently have not the time to create a series for BlackBerry 10 as I did for Qt. I will write a followup post later, but in September the Papers for Chicago will get a little coverage on this blog for sure.

I've uploaded the ListViewItemProvider class, in case you want to take a look...

Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!