Reload on QML code change 28 August 2023

Developing a Qt app with QML has the advantage that there exists an interpreter for QML code. This enables to reload code without restarting the Qt application unless there is a change in compiled code. We present a novel and simple implementation of such a reloading functionality. This can noticeably speed up development of QML applications.

There are several existing approaches described and found online. Most of them use a separate class to implement the functionality. This is extensible, but requires to create and reference extra files.

Here, we strive for short code that is integrated in a simple way by adding it to the main function. We assume that

  • all QML code has ‘qml’ and ‘js’ extensions,
  • is added to the project’s qrc file,
  • and is stored relative to the project root.

We conditionally include


#ifdef QT_DEBUG
#include <QFileSystemWatcher>
#include <QDirIterator>
#endif

and add a few lines to the main function; located after declaring QQmlApplicationEngine engine and setting its import path, and located before entering Qt’s application loop:


#ifdef QT_DEBUG
engine.addImportPath(QFileInfo(__FILE__).absolutePath());
QFileSystemWatcher watcher;
for(QDirIterator it(":", {"*.qml", "*.js"}, QDir::Files, QDirIterator::Subdirectories); it.hasNext();)
    watcher.addPath(QFileInfo(__FILE__).absolutePath() + it.next().mid(1));
QObject::connect(&watcher, &QFileSystemWatcher::fileChanged, [&](){
    engine.clearComponentCache();
    engine.load(QFileInfo(__FILE__).absolutePath() + "/main.qml");
});
#endif

First, we ensure that the current file path is the first and foremost import path for the QML engine. This ensures that changed files are loaded from disk, not from the compiled qrc file. Then, the qrc file is scanned for the ‘qml’ and ‘js’ files. These are added to Qt’s file system watcher to listen for updates. On update, the QML engine cache is cleared and the main.qml file is loaded again.

We note that when reloading several files, the code is reloaded several times. This redundancy is avoided by collecting the changes and reloading only once. We attain this by adding a single shot QTimer.

#include <QTimer>


#ifdef QT_DEBUG
engine.addImportPath(QFileInfo(__FILE__).absolutePath());
QFileSystemWatcher watcher;
for(QDirIterator it(":", {"*.qml", "*.js"}, QDir::Files, QDirIterator::Subdirectories); it.hasNext();)
    watcher.addPath(QFileInfo(__FILE__).absolutePath() + it.next().mid(1));
QTimer asyncifyWatcher;
asyncifyWatcher.setSingleShot(true);
QObject::connect(&watcher, &QFileSystemWatcher::fileChanged, [&](){
    asyncifyWatcher.start(10);
});
QObject::connect(&asyncifyWatcher, &QTimer::timeout, [&](){
    engine.clearComponentCache();
    engine.load(QFileInfo(__FILE__).absolutePath() + "/main.qml");
});
#endif