Monday, November 24, 2014

QML wrappers for native Android components

TL;DR It might be simpler than you think to wrap native UIs with the QtQml module, but it does require a good understanding of the underlying platform

In my previous post, I showed how you can work with platform-native classes and use the QtAndroidExtras module to create UIs from code. One of the downsides was the extra code complexity you had to manage. The "Qt way" of doing UIs is, however, by using QML, for the reasons of brevity, development speed and flexibility. I cobbled together a custom Android component module to demonstrate this (sources available on GitHub) - the 40+ thick line C++ UI example suddenly becomes a lot more readable and fun to work with.



Qt does have a solution for native-looking UIs on Android in the form of QtQuick.Controls, so my reasoning here is more to show

  • what are the advantages of custom components
  • how custom components are done
  • what are the pitfalls of custom component sets
What are the advantages? If there is no native-looking component set, it's quite clear - you simply have no alternative. If QtQuick.Controls does exist for your platform (see the case of Android), there is still a number of reasons why you might consider going custom. First, you get the full functionality and availability of the platform-native set. QtQuick.Controls gives you something that is closer to a lowest common denominator, with a lot of platform-specific components/functionality missing and occasionally some input method quirks. By skipping this, you create UIs which are not just "close-to-platform-native", but effectively *are* platform native. Second, you get the full performance. By having a wrapper, there will still be a slight penalty for loading Qt, and parsing/constructing the QML objects, but there is no compromise once the UI is instantiated. I personally also like that you can use the platform-native documentation and examples, since you're just using QML syntax, but class names, properties and general usage pattern remain the same as on the platform.

How does one go about creating a new component set? As you've seen from the first QML snippet, all we really need is the QtQml module. If you start creating with a completely custom *native* component set (ie not deriving from an existing QtQuick or QtQuick.Controls base) you get a really basic set of components to work with, namely Component, QtObject, Binding, Connections, Timer. All you native components will be actually QObject derived C++ objects, registered with the QML system, as described here. The classes themselves are still plain Qt classes, here's how my activity.h looks like:



After that, you need to register every such object with the QML engine (usually in your main.cpp) :

qmlRegisterType("OutQross.Android", 0, 1, "Activity");

With this we can start instantiating our custom objects from QML. The QtObject class/element unfortunately does not have a lot of the functionality normally coming from Item (including having a default children property), so I created an OutQrossItem helper class for this purpose, which is the base class for all my Qml elements.



Finally, let's see the gritty innards of a custom component class, an Android Button in this case



As you can see, I still use the same UI inflate paradigm as Android to actually instantiate the button (this way we also avoid the question of the order of object initialization or property settings), and you can also see how a property can we wrapped. This means that the full power of QML - bindings, signals/slots and JS are available to us as the properties are mapped to the native components. This is crucial if you are building your own component sets, as otherwise you're just making a syntax converter - and this is also the answer to how this is different to just converting QML to Android .XML UIs or vice versa.

For completeness' sake, let's mention that the .APK was 6.9MB and the memory usage of the HelloWorld app was 61510kb (about halfway between the version in my previous blog post and the "full" QtQuick experience)

There are some pitfalls as well. First, while not rocket science, it's actual work. If you have an extensive GUI widget set or library (like Android), you probably want to use or write some sort of wrapper code generator as manually creating and maintaining a full wrapper set can be daunting. Second, there might be specific UI caveats - for example, in the case of my Android example, to keep it simple, I'm not actually running on the UI thread. This means that in my particular implementation while native component and property updates happen via the bindings, they might not immediately appear on the screen. This can be resolved, but it does require a very good understanding of how the native platform works. My advice is that you shouldn't consider writing wrappers because you don't know how the platform-native UIs work - it should primarily be done to accelerate future development and prototyping.

2 comments:

  1. Nice blog about QML wrappers for native Android components.Android Training in chennai

    ReplyDelete
  2. Given so much info in it, These type of articles keeps the users interest in the website, and keep on sharing more .
    Android Training

    ReplyDelete