At Tue Apr 17 04:09:07 CEST 2012 Alan Alpert started a flamewar about QML. Whoops. But the thread did reach an appropriate conclusion as to where the rants should go; put it on an aggregated blog. Since I forgot to do that back then, here’s an expanded version of the essay now. The content is still accurate, but I’ve added two more paragraphs on the end to elucidate it further.
QML is an important part of the QML user story, but not the only part. Besides being implemented in C++, the full Qt/QML story relies on Qt/C++ being the core of the application. So it helps to know what roles each language is supposed to play in the story. This article is written from the QML perspective, so the C++ referred to here is about when you implement your QML facing APIs in C++ (or implement the application backend in C++, for your QML frontend). The QML referred to here refers to the QML users of your API. This article is focused on the core user story for QML, a C++ application with a QML UI, which happens to have the same design as implementing a QML module for other applications to use.
QML is the User Experience (UX or UI, User Interface) layer. This means that the visual representation of the application is done with QML. It also means that logic reacting to user input is done in QML. This is highly advantageous in allowing the user interaction to affect the visual representation of the application directly. In order for user input logic to be done in QML, the application’s functionality usually needs to be exposed in some way. This allows for the QML code to map the UX to the application abilities in a scripting language, for more versatility and rapid application development.
C++ is the data layer. This means that data manipulation and often the real functionality of the application remains in C++. What is exposed to QML is the UX abstraction of the functionality, such s an action you might find on a toolbar (e.g. save file). The programmatic instructions for implementing this on a computer remain in C++ (e.g. opening a file, writing data from the main view to the file, closing the file). Some logic can be done in the UX layer (e.g. passing the main view to the function instead of the C++ plugin assuming this) but only UX level abstractions should be exposed to QML. The logic of how to manipulate your application’s data, if it is not exposed to the end user, should not be exposed to QML.
The split is right where a QML module’s API usually lies (if the module is written in C++). So your API should be written with this split in mind. What this means in terms of API design is that such an API should expose results and high-level functionality to QML, anything which might be exposed directly to a user to manipulate. If the functionality is sufficiently low-level that it would never be directly called from the UI layer, try not to expose it. Instead, expose the higher level results that are expected so that more of the imperative logic can be done in C++, and to simplify any QML using it.
In the Qt/QML story it is important that you can always drop down to a (usually separate) C++ API for anything more complex or involved than is provided in the QML API. For example, in QtQuick 1.x subclassing QDeclarativeItem was the only solution for imperative painting in a QML scene because that usecase was not common enough for the first iteration (of course, QtQuick 2.x has the Canvas API). This is particularly true at this early development stage, because there is always a C++ application entry point. It is okay if a rare use-case is only serviced by the C++ API. There should usually be a C++ API as well as the QML API, although it may be shared with the QML API for implementation reasons.
As an example, consider file I/O. Your starting point for a QML file I/O plugin would be the high level QFile API, not the lower level QIODevice API. But even QFile exposes a lot of imperative object state and niche functionality. A QML File object could be as simple as filePath and contents properties to cover the majority of usecases. Upon changes to the filePath property (or item destruction) the underlying QFile could be closed and reopened. It could read the file contents into a buffer at that time for updating the contents property when read by QML. Changes to the contents property are treated as writes to the file. There is some inefficiency in this implementation compared to a C++ implementation, but (for this example at least) large files are a minority use case which is catered for using the C++ API. High-performance usecases will usually need to use the C++ API directly for the efficiencies given by having fine grained control. A Hex editor would likely need to expose its own file handling for the binary view of file data, but could still use the simple File element for a preview pane.
In that example you might notice something. By providing a simplified, UI driven API you’ve “arbitrarily” constrained the API users. Unlike the fully featured and generic C++ API, you can’t easily take the QML API and then implement all possible file access usecases. Not just in terms of an efficient implementation, but in terms of simply being able to do things like display binary data the QML API just doesn’t do that. This raises the question: Why is this arbitrarily limited API better than exposing all of QFile, which is less limited and easier to implement? The answer is to look at the place that QML has in the Qt/QML story. Remember that the idea is that you can always drop down to C++ if you need something not provided in QML. Given that there’s little point in just exposing the C++ API to QML. If you do that, you’ve made things a little easier for C++ developers but you’ve made the code less readable and the API less approachable to QML developers who are actually using it as a declarative language. Instead of adding all that extra “C++” API to QML, you can add a QML API which actually fits into QML (meaning it’s declarative, it aids rapid prototyping and works for common usecases). The QML API provides real benefit from the declarative styling leading to developer convenience, and nothing is prevented because the C++ APIs all still work. In the common user story this means that the intial QML API might only be useful in the rapid prototyping stage, and then a new QML API is exposed when you turn that into a real application. But that new file API your application exposes for your usecases should be just an easy QML wrapper of Qt’s provided C++ functionality, in a way that makes it easy for your UI teams to iterate on your specific application. So the “new API” is just the reality of the application developer needing to expose application functionality for the final product – not something that can ever be completely abstracted away from the development process. What QML can do is help abstract that away from the UI developer, who can write the UI without needing to implement any application functionality. That part is just handed to him as the QML API, even as it evolves with the product.
Recent developments, like the discussion of the QML runtime, might make people think that the “always drop down to C++” aspect of the QML story is a temporary element from it’s immaturity. But since that aspect is one of the great strengths of QML, the reality is that it is here to stay. Even when you write your application as main.qml, with a
at the top you can import functionality fom C++ using modules. One of the early versions of QML deployment was looking to be exactly that – applications were started with “qml main.qml” and all application functionality was imported via “import ApplicationCore 1.0” (with ApplicationCore being in the same directory and shipped next to main.qml of course). So whether you have a C++ launcher or launch via the QML runtime, you always have the option to access C++ plugins. Until we implement import restriction so it can be used as an application scripting language of course 😉 .