This article explains how to take advantage of the singleton pattern in QML, to develope a clean and a structured utility class.

The singleton pattern is a software design pattern that ensures a class has one instance, providing a global point of access to it. Due to its global access, it is one of the most controversial pattern, and can lead to design problems if used incorrectly.

Advantages:

  • it is initialized only when it’s requested for the first time
  • it can simplify code by making easier to access shared resources
  • it can improve performance by avoiding the need to create multiple instances of a class

Disadvantages:

  • it can make more difficult to reason about the behavior of the application
  • it can lead to design problems, especially when managing multiple threads or concurrent accesses to shared resources
  • it violates the Single Responsibility Principle. The pattern solves two problems at the time.

NOTE: As a rule of thumb, don’t overuse it.

One way to take advantage of this pattern is to reduce the number of global classes by piggybacking on existing ones.

JavaScript resource libraries

For example, let’s suppose to have a QML application which uses some resources that must be accessible from anywhere, like translation strings, icons or other static configurations:

.
└── src
    ├── assets  # Contains the application's icons
    │   ├── icons
    │   │   ├── create_icons_js_resource.sh    # bash utility script to generate a js resource file for icons
    │   │   └── tabler
    │   │       ├── LICENSE.txt
    │   │       ├── moon.svg
    │   │       └── sun.svg
    │   └── icons.qrc
    ├── cpp     # Contains c++ code
    │   └── main.cpp
    └── qml     # Contains qml/js code
        ├── components  # Custom components
        │   └── Button.qml
        ├── conf        
        │   ├── App.qml     # Application singleton
        │   └── Theme.qml
        ├── javascript 
        │   ├── config.js   # Config resource library
        │   ├── icons.js    # Icons resource library
        │   └── strings.js  # Strings resource library
        ├── main.qml
        ├── qmldir  # Module components
        └── qml.qrc

Those resources can be imported in multiple QML files as javascript scripts using the import keyword. However, because by default, imported javascript files share their context with the QML component that imports them, a new copy of those file is created.

So, to save significant amounts of memory, each file can be marked as a .pragma library, which makes them shared resource libraries. In the example, 3 resource libraries are created:

  • one to store all the strings used in the application (strings.js):

    .pragma library
    
    var TR_APPLICATION_TITLE = qsTr("Improving QML code readability with singleton")
    var TR_BUTTON_DARK_THEME = qsTr("Enable dark theme")
    var TR_BUTTON_LIGHT_THEME = qsTr("Enable light theme")
    
  • one to store the static configurations of the application (config.js):

    .pragma library
    
    var VERSION = "v1.0.0"
    
  • one to store the paths of the application icons (icons.js):

    .pragma library
    
    var prefix = "qrc:/assets/icons/tabler"
    
    var MOON = Qt.resolvedUrl(prefix+"/moon.svg")
    var SUN = Qt.resolvedUrl(prefix+"/sun.svg")
    

Every time one of this resource would be needed in a file, it would be imported as follows:

import "path/to/strings.js" as Strings
import "path/to/config.js" as Config
import "path/to/icons.js" as Icons

Now, imagine a complex application which has tons of files that needs more than a bunch of resource files: the number of imports and references increases dramatically, making refactor and usage annoying.

The number of references and imports can be reduced using a singleton which wraps them all: this will create a single access to all resources, reducing code complexity and improving readability.

NOTE: Keep always in mind the KISS principle and avoid to create god classes.

QML Singleton

In the example, the QML singleton App.qml is created to store all the above references:

pragma Singleton

import QtQuick 2.0

import "../javascript/strings.js" as ApplicationStrings
import "../javascript/config.js" as ApplicationConfig
import "../javascript/icons.js" as ApplicationIcons

Item {
    readonly property var config: ApplicationConfig
    readonly property var strings: ApplicationStrings
    readonly property var icons: ApplicationIcons
}

To be able to use this singleton from any QML file:

  • create a module, using the qmldir file:

    module qml
    
    singleton App 1.0 conf/App.qml
    
  • add the following line, in main.cpp, to allow QML engine to load custom modules:

    engine.addImportPath(":/");
    
  • import it in QML using:

    import qml 1.0
    

Now, those resources can be used in QML without any other import:

import QtQuick 2.12
import QtQuick.Window 2.12

import qml 1.0

Window {
    width: 640
    height: 480
    visible: true
    title: App.strings.TR_APPLICATION_TITLE + " - " + App.config.VERSION

    ...
}

Source code is available here.

References