C

QML best practices

Your application's QML code may have a significant impact on the flash and random access memory footprint. By writing clean QML code (without duplication), you reduce the amount of C++ code generated and the flash memory footprint of it. The following sections describe other techniques that you could use to reduce memory footprint and improve performance.

Create reusable components

Instead of duplicating the same code patterns in several places, consider encapsulating the pattern in a separate QML file.

For example, the code below has a list of labels, each containing some text and an image:

Column {
    spacing: 15
    anchors.centerIn: parent

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 1"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img1.png"
            }
        }
    }

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 2"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img2.png"
            }
        }
    }
}

You can simplify this code by creating the Label.qml file, with the configurable properties or property aliases as shown here:

import QtQuick 2.15

Rectangle {
    property alias imageSource: imageItem.source
    property alias text: textItem.text

    width: 250
    height: 120
    color: "#e7e3e7"

    Row {
        anchors.centerIn: parent
        spacing: 10

        Text {
            id: textItem
            anchors.verticalCenter: parent.verticalCenter
            color: "#22322f"
            font.pixelSize: 22
        }

        Image {
            id: imageItem
            anchors.verticalCenter: parent.verticalCenter
        }
    }
}

You can reuse this QML component in the original QML code and avoid duplication:

Column {
    spacing: 15
    anchors.centerIn: parent

    Label {
        text: "Entry 1"
        imageSource: "img1.png"
    }

    Label {
        text: "Entry 2"
        imageSource: "img2.png"
    }
}

Limiting PropertyChanges

A QML file with a lot of states and many properties that are affected by those states through PropertyChanges, leads to generating large and complex C++ code. The size of the generated code will be N x M, where N is the number of states and M is the number of unique properties updated by PropertyChanges in those states.

Here's an example with just two states and two properties, but imagine that there would be a lot more similar states to choose between a variety of views in the same QML component:

Item {
    state: "0"
    states: [
        State {
            name: "0"
            PropertyChanges { target: viewA; visible: true }
        },
        State {
            name: "1"
            PropertyChanges { target: viewB; visible: true }
        }
    ]
    ViewA {
        id: viewA
        visible: false
    }
    ViewB {
        id: viewB
        visible: false
    }
}

You can optimize this by setting the visible property of the views directly based on the state:

Item {
    id: root
    state: "0"
    states: [
        State { name: "0" },
        State { name: "1" }
    ]
    ViewA {
        id: viewA
        visible: root.state == "0"
    }
    ViewB {
        id: viewB
        visible: root.state == "1"
    }
}

Avoid empty container items

The Item type can be useful for grouping other items, making it possible to set their visibility and position in a combined way. Limit the use of container items as they increase the memory usage. For example, the outer Item in the following code snippet is unnecessary:

Item {
    Image {
        anchors.fill: parent
        source: "img.png"
    }
}

Instead, you can use the contained Image item directly:

Image {
    anchors.fill: parent
    source: "img.png"
}

Load components dynamically

Your application might contain complex QML components with many items, which are visible at different times. You can reduce RAM usage by loading such components dynamically using Loader type.

Unload existing hidden items explicitly before loading new items, to avoid memory peaks. Depending on the applications UI design and memory constraints, ensure that only a select no. of items are loaded at any given time. The following example demonstrates how you can ensure only one page in the SwipeView is loaded into memory at any given time:

SwipeView {
    id: theSwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    function updateLoaderStates() {
        console.log("updating loader states ...")
        if (theSwipe.currentIndex === 0) {
            loader1.source = ""
            loader0.source = "FirstPage.qml"
        } else if (theSwipe.currentIndex === 1) {
            loader0.source = ""
            loader1.source = "SecondPage.qml"
        }
    }

    Component.onCompleted: updateLoaderStates()
    onCurrentIndexChanged: updateLoaderStates()

    Loader {
        id: loader0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader {
        id: loader1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

As a general rule, do not rely on the evaluation order of bindings. In the following example, you have no control on the order in which the items are loaded and unloaded. This may result in temporarily allocating memory for both pages of the application:

SwipeView {
    id: mySwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    onCurrentIndexChanged: {
        console.log("index changed ...")
    }

    Loader
    {
        source: "FirstPage.qml"
        active: mySwipe.currentIndex === 0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader
    {
        source: "SecondPage.qml"
        active: mySwipe.currentIndex === 1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

Reduce the number of visual components

Each visual item typically carries some processing and rendering overhead at runtime. If possible, reduce the number of visual items required to compose the UI. Below are some examples of how you can do this.

Reduce overlapping images

If two images are always overlapping in the UI, it might be better to combine them into a single image. A lot of overlapping images can reduce performance and consume more memory. For example, the inner.png in the following code snippet is smaller than the outer.png image:

Image {
    id: outer
    source: "outer.png"
}
Image {
    anchors.centerIn: outer
    source: "inner.png"
}

Instead of using them separately, combine inner.png and outer.png into a single image:

Image {
    source: "combined.png"
}

If static text is overlapping the image, it is worth adding that into the image, instead of using separate Text or StaticText items.

Reduce the Text items

Avoid arranging many Text items in a row if you can combine them into a single Text item. For example, the following code snippet shows two Text items arranged in a row:

Row {
    Text {
        text: "Temperature: "
    }
    Text {
        text: root.temperature
    }
}

You can combine them into a single Text item:

Text {
    text: "Temperature: " + root.temperature
}

Reduce the number of bindings

Reducing the number of bindings will save ROM

Use implicit dimensions

You can reduce the number of bindings by using implicit dimensions when possible

Using implicit dimensions for images

Create your images with the correct dimensions so you do not have to specify width and height properties every time you use the image.

Image {
    width: 64
    height: 64
    fillMode: Image.pad
    source: "image/background.png"
}

Instead use implicit width and height:

Image {
    source: "image/background.png"
}

Using implicit dimensions for component

You can reduce the number of bindings on often used components by using implicit dimensions.

For example, consider IconButton defined in the IconButton.qml, has no implicit size:

MouseArea {
    property alias iconSource: img.source

    Image {
        id: img
        source: ""
    }
}

This forces the user of the component to specify width and height of the component

IconButton {
    width: 64
    height: 64
    iconSource: "home.png"
}

Instead creating the IconButton like below:

MouseArea {
    implicitWidth: img.implicitWidth
    implicitHeight: img.implicitHeight

    property alias iconSource: img.source

    Image {
        id: img
        source: ""
    }
}

It reduces the number of bindings.

IconButton {
    iconSource: "home.png"
}

In this case you increased the size of the component but the overall ROM saving will be greater if the component is often used.

Text and Image visibility

Control visibility of Text and Image items in you application, by using an empty string as values for their text and source properties respectively.

For example, the component defined by the following code:

Item {
    property alias iconVisible: img.visible
    property alias textVisible: txt.visible

    property alias imageSource: img.source
    property alias text: txt.text

    Image {
       id: img
       source: ""
    }

    Text {
        id: txt
        text: ""
    }
}

You can use such a component in the following way:

MyComponent {
    textVisible: false
    text: ""
    iconVisible: true
    imageSource: "images/background.png"
}

You can also achieve the same result, without using the visibility properties:

Item {
    property alias imageSource: img.source
    property alias text: txt.text

    Image {
       id: img
       source: ""
    }

    Text {
        id: txt
        text: ""
    }
}

When you use the component as seen in the following example, the image item is visible but the Text item is not:

MyComponent {
    imageSource: "images/background.png"
}

Use states to pack properties

Apply this approach to most used components. For example, the following Header component:

Row {
    property alias button1Text: btn1.text
    property alias button2Text: btn2.text
    property alias button3Text: btn3.text

    Button {
        id: btn1
        text: ""
    }
    Button {
        id: btn2
        text: ""
    }
    Button {
        id: btn3
        text: ""
    }
}

You will have to specify 3 bindings:

Header {
    button1Text: "Back"
    button2Text: "OK"
    button3Text: "Info"
}

Instead you can pack those properties into state:

Row {
    Button {
        id: btn1
        text: ""
    }
    Button {
        id: btn2
        text: ""
    }
    Button {
        id: btn3
        text: ""
    }

    states: [
        State {
            name: "VariantA"
            PropertyChanges {
                target: btn1
                text: "Back"
            }
            PropertyChanges {
                target: btn2
                text: "OK"
            }
            PropertyChanges {
                target: btn3
                text: "Info"
            }
        }
    ]
}

This reduces the number of bindings to only one binding:

Header {
    state: "VariantA"
}

Keep signals simple

Always simplify signals when possible. For example in case of having many buttons inside a component MyComponent.qml that are logically similar:

Item {
    id: root

    signal button1Clicked
    signal button2Clicked

    Row {
        Button {
            text: "Ok"
            onClicked: {
                root.button1Clicked()
            }
        }

        Button {
            text: "Cancel"
            onClicked: {
                root.button2Clicked()
            }
        }
    }
}

You will need 2 bindings, one for each signal:

Rectangle {
    MyComponent {
        onButton1Clicked: {
            console.log("Ok")
        }
        onButton2Clicked: {
            console.log("Cancel")
        }
    }
}

Instead use one signal that passes an index that identifies which button was clicked

Item {
    id: root

    signal buttonClicked(index: int)

    Row {
        Button {
            text: "Ok"

            onClicked: {
               root.buttonClicked(0)
            }
        }

        Button {
            text: "Cancel"

            onClicked: {
               root.buttonClicked(1)
            }
        }
    }
}

That way you will need 1 binding only:

Rectangle {
    MyComponent {
        onButtonClicked: {
            switch(index) {
            case 0: {
                console.log("Ok")
                break;
            }
            case 1: {
               console.log("Cancel")
                break;
            }
            }
        }
    }
}

Reduce the size of models

Do not have all delegate properties in the model. Reduce the amount of used properties by specifying it directly in the view.

For example, do not create a model like:

Rectangle {
    property ListModel myModel : ListModel {
        ListElement {
            textcolor: "blue"
            name: "John"
            age: 20
        }
        ListElement {
            textcolor: "blue"
            name: "Ochieng"
            age: 30
        }
    }

    ListView {
        anchors.fill: parent
        model: myModel
        delegate: Text {
            width: 50
            height: 50
            color: model.textcolor
            text: "Name: %1 Age: %2".arg(model.name).arg(model.age)
        }
    }
}

If the value of textcolor property is the same for all data, remove it from your model and declare it as a property to reduce the size of your model and avoid unnecessary duplicates:

Rectangle {
    property ListModel myModel : ListModel {
        ListElement {
            name: "John"
            age: 20
        }
        ListElement {
            name: "Ochieng"
            age: 30
        }
    }

    property string textcolor

    ListView {
        anchors.fill: parent
        model: myModel
        delegate: Text {
            width: 50
            height: 50
            color: textcolor
            text: "Name: %1 Age: %2".arg(model.name).arg(model.age)
        }
    }
}

Sharing big ListModel

Use ListModel property in qml Singleton to share ListModel between different parts of the application. This will help to save ROM.

// AppConfig.qml
pragma Singleton
..
QtObject {
    property ListModel mySharedModel: ListModel {
       ListElement { bgcolor: 'red' }
       ListElement { bgcolor: 'yellow' }
       ListElement { bgcolor: 'blue' }
       ListElement { bgcolor: 'green' }
       ListElement { bgcolor: 'orange' }
       ListElement { bgcolor: 'black' }
       ListElement { bgcolor: 'gray' }
       ...
    }
}
// Page1.qml
Repeater {
    model: AppConfig.mySharedModel
    delegate: ..
}

// Page2.qml
ListView {
    model: AppConfig.mySharedModel
    delegate: ..
}

Available under certain Qt licenses.
Find out more.