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:
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:
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:
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:
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: .. }
Split large ListModels
If you have a large ListModel with many properties for each ListElement, consider splitting the data vertically into multiple lists of QML Basic Types. This will save ROM and reduce the amount of generated C++ code for bindings. See Reduce the number of bindings for more information.
Example:
ListModel { id: myModel ListElement { name: "Hans"; age: 25; city: "Berlin"; x: 10; y: 20; } ListElement { name: "Marie"; age: 30; city: "Paris"; x: 15; y: 25; } ListElement { name: "Luca"; age: 35; city: "Rome"; x: 20; y: 30; } ListElement { name: "Eva"; age: 40; city: "Madrid"; x: 25; y: 35; } // ... } Repeater { model: myModel Text { text: model.name + ", " + model.age + ", " + model.city } }
Qt Quick Ultralite generates 20 (5 * number of ListElements
) bindings for each property in the earlier example. You can reduce it to only 4 by using the following approach:
readonly property list user_name: ["Hans", "Marie", "Luca", "Eva", ...] readonly property list user_age: [25, 30, 35, 40, ...] readonly property list user_city: ["Berlin", "Paris", "Rome", "Madrid", ...] readonly property list user_position: [Qt.point(10, 20), Qt.point(15, 25), Qt.point(20, 30), Qt.point(25, 35), ...] Repeater { model: parent.names.length Text { x: user_position[index].x y: user_position[index].y text: user_name[index] + ", " + user_age[index] + ", " + user_city[index] } }
Note: Qt Quick Ultralite generates bindings for each property in a list of objects. Use this approach with {QML Basic Types} only.
Currently, the length
property is copied and not bound to the model. This means you should explicitly update the model value to make sure it adapts to the list's size.
Available under certain Qt licenses.
Find out more.