Live Reloading or Hot Reloading with QML

Live reloading or hot reloading is all the rage these days. One of the best examples of hot reloading is React Native.

Check out a video of it here:

Live reloading is extremely useful to quickly develop and iterate over the user interface aspect of an application.

The majority of Qt / QML developers I have met (myself included, until recently) underestimate the capability of hot reloading QML applications. We are so accustomed to building and running an application, even for the slighest of changes (width, height, color, and other static data).

There are many solutions to the hot reloading problem in Qt + QML. They all have their own advantages and disadvantages. I prefer to use a combination of all the below to accomplish a variety of UI tasks for a project:

Terrarium

Terrarium is a live reloader documented here.

Advantages:

  • Quick prototyping with a built-in editor.
  • Great for QML interviews.

Disadvantages:

  • Not useful for projects as it can only read from the TextEdit element that is built-in to the application.

Qml LiveReload

Qml LiveReload is available here.

After building, the target is placed at /usr/local/bin (for some reason..)
To use,

/usr/local/bin/qmllive /path/to/file.qml

Advantages:

  • Easy to use, simply build and execute a QML file with the binary specified above.
  • Hard to use with large projects, does not preserve states

Disadvantages:

  • Out of build target.. builds to /usr/local/bin/qmllive
  • Reloads the entire application

QmlLive

I lightly covered QmlLive here.

Advantages:

  • Application preserves workspace, you can easily open and close the app and restore your workspace
  • Allows deployment of a runtime to an embedded device to see a UI reload live on a different device.

Disadvantages:

  • Reloads the entire application
  • UI states are not preserved upon reload

Custom Loader + QQmlEngine Solution

This is my preferred solution. I believe it is the most flexible one as it provides reload capability to individual pieces of a project's user interface; unlike some of the aforementioned solutions which reload the entire user interface.

Before diving into the code that powers this method of live reloading, let's investigate how the QmlEngine works with respect to the loading of QML elements.

In QML, a Component is a template that defines an object. A Component can be used to build objects that have a well-defined structure and interface.

Component {
    id: _componentIconLabel
    
    Row {
        property var icon
        property var text
        Image {
            width: 50; height: 50
            source: parent.icon
        }
        Text {
            text: parent.text
        }
    }
}

Button {
    onClicked: {
        var iconLabelObject = _componentIconLabel.createObject(root);
        iconLabelObject.text = "Hello World"; 
        // NOTE: These properties can be set before the 
        // element is created by using the createObject 
        // method's second argument
    }
}

In the example above, we already created the Component by declaring it in QML. You can also create a Component by loading a file.

Once a Component is created, it is cached in the QmlEngine.

How does this cache work? Let's dig a little deeper into the sources.

  1. Component uses the QmlEngine's QmlTypeLoader to get the type of what is being loaded into a Component. More information can be read in the Qt sources here.

  2. QmlTypeLoader provides a getType method that checks a cache (QmlTypeCache), if there is a hit it returns cached data, else, it loads in new data.

To allow for hot or live reloading, it is crucial for us to ignore the cache and reload from disk. The criteria for checking cache is not particularly sophisticated as every type is registered by URL / type name.

Since we do not want modify the criteria for how Component caching works in Qt sources, we can work around this cache by simply clearing it when a reload is required.

QmlEngine provides two methods for manipulating the QML type component cache.

QQmlEngine::trimComponentCache() - Trims component cache entries for components that are no longer used. This is a very difficult criteria to satisfy as components are generally used widely throughout a QML project.

See Qt sources here

QQmlEngine::clearComponentCache() - Clears the entire component cache. If an element is still used, it is at risk of causing warnings throughout the UI as when the same type is loaded again, it will be loaded into a new cache entry. This new cache entry and the old active element will no longer match.

This is a small inconvenience for the benefit of hot reloading. By clearing the entire component cache, we can simply reload a QML file to view updated changes.

See Qt sources here.

Let's get down to business.

There are a few ways you can implement this:

  • Option A: Build a custom Component loader in Qt C++, reference QmlEngine directly in C++
  • Option B: Extend the QML Loader and expose a reference to the QmlEngine to QML

I prefer Option B, so let's do it:

First, let's create an example project with a Window, a Loader, and a Component.

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Loader {
        id: _loader

        function reload() {
            source = "";
            source = "Circle.qml";
        }

        anchors.centerIn: parent
        source: "Circle.qml"
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            _loader.reload();
        }
    }
}

Add a MouseArea and a method in the loader called reload. We will use these to prove that the QmlEngine and its associates are caching types.

In this example, we are creating a Loader and load in file called Circle.qml:

Circle.qml

import QtQuick 2.9

Rectangle {
    width: 300
    height: 300
    radius: width / 2

    color: "blue"
}

Run the UI and you will see this:

Keep the UI open. Now, modify Circle.qml to make the Rectangle color red:

Circle.qml

import QtQuick 2.9

Rectangle {
    width: 300
    height: 300
    radius: width / 2

    color: "red"
}

Now that the Circle is red, click on the UI to reload the Loader.

Notice how the Circle remains blue? This Loader is being reloaded yet the Circle is still blue. This is proof that the Component created from Circle.qml is cached. It's also apparent that the cache is not keyed by the contents of Circle.qml.

Let's keep working until we can figure out how to make the Circle change colors when we click the UI.

Above, we learned about the methods available in the QmlEngine to trim and clear the cache in the QmlEngine. Let's use some of these methods.

To use a method from the QmlEngine in QML land, we must expose it. Let's do it via context properties.

Here is our auto-generated (by project template) main.cpp:

original main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

Let's use context properties to expose the engine to QML. Context properties are simply magic global variables that are available in all of QML without the need of importing anything.

modified main.qml

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include <QQmlContext>

int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("$QmlEngine", &engine);

    engine.load(QUrl(qgetenv("MAIN_QML")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

In this code, we are exposing a global variable called $QmlEngine as a context property of the QmlEngine's root context. This allows the $QmlEngine variable to be used from any QML file loaded inside this this particular engine.

Also note how we replaced the qrc:// URL with a qgetenv. We do not want Qt to compile our resources into a binary QRC file as this would require us to rebuild the project when changing a QML file.

I have removed the use of QRC in almost every large-scale project I've worked on. Compiling QML files into resource files is very detrimental to developer productivity. Compiling QML files into resources can easily be enabled for release or production builds and avoided in developer builds.

We should run our application with the environment variable specified, e.g:

MAIN_QML=../HotReloading/main.qml ./bin/HotReloading

Now that we have a reference to the QmlEngine, let's invoke the QQmlEngine::clearComponentCache when we are reloading the Loader element in main.qml:

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Loader {
        id: _loader

        function reload() {
            source = "";
            $QmlEngine.clearComponentCache();
            source = "Circle.qml";
        }

        anchors.centerIn: parent
        source: "Circle.qml"
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            _loader.reload();
        }
    }
}

Let's try this again. Build your code and run the UI again.
Modify the color of the Rectangle in Circle.qml.

Click the circle. Did it change colors?

Nope! Now we got a warning:

qrc:/main.qml:15: TypeError: Property 'clearComponentCache' of object QQmlApplicationEngine(0x7fff58907a98) is not a function

What gives? It seems that QQmlEngine::clearComponentCache is not allowed to be invoked by QML. It is a public method, but it does not have the Q_INVOKABLE flag.

Let's create a utility class that exposes an aliased function that calls QQmlEngine::clearComponentCache. These kinds of hoops are common when attempting to expose Qt functionality to QML.

We have two options here, we can either create a brand new class that extends QObject or we can create a brand new class that extends QQmlApplicationEngine. The latter is simpler as we don't need to pass in the QQmlEngine object.

Create a new file called EnhancedQmlApplicationEngine:

enhancedqmlapplicationengine.h

#pragma once

#include <QQmlApplicationEngine>

class EnhancedQmlApplicationEngine : public QQmlApplicationEngine
{
    Q_OBJECT
public:
    explicit EnhancedQmlApplicationEngine(QObject *parent = nullptr);

    Q_INVOKABLE void clearCache();
};

enhancedqmlapplicationengine.cpp

#include "enhancedqmlapplicationengine.h"

EnhancedQmlApplicationEngine::EnhancedQmlApplicationEngine(QObject *parent)
    : QQmlApplicationEngine(parent)
{

}

void EnhancedQmlApplicationEngine::clearCache()
{
    this->clearComponentCache();
}

Note how we had to name the method clearCache instead of clearComponentCache since the latter was already taken by the super class, QQmlEngine by proxy of QQmlApplicationEngine.

Use this new engine in main.cpp:

main.cpp

#include <QGuiApplication>
#include <QQmlContext>

#include "enhancedqmlapplicationengine.h"

int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QGuiApplication app(argc, argv);

    EnhancedQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("$QmlEngine", &engine);

    engine.load(QUrl(qgetenv("MAIN_QML")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

Update your reload method in main.qml to call our new clearCache method:

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Loader {
        id: _loader

        function reload() {
            source = "";
            $QmlEngine.clearCache();
            source = "Circle.qml";
        }

        anchors.centerIn: parent
        source: "Circle.qml"
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            _loader.reload();
        }
    }
}

Voila! Build your code again.

Run the UI.

Now, you can scatter these Loader elements with a custom reload method throughout your UI. Whenever you need to reload, you can. This allows for rapid UI development, especially when building out individual widgets or scenes.

This Hot Reloading solution can be extended:

  • Use a QFileSystemWatcher class to track the loaded file for changes. When a change is signaled, reload the Loader.
  • Use a Timer to reload constantly while developing.

The source code is available on the qml.guide GitHub repository.