Home Qt Quick Best Practices: Using Components

Qt Quick Best Practices: Using Components

 
Introduction
QML provides a useful concept to split up code called ‘Components’. The easiest way to create a re-usable component is to add a new file to the working directory of the main QML file.
Example.qml:

import QtQuick 1.0
Rectangle {
}

main.qml:

import QtQuick 1.0
Example {
}

Components can also be packaged as modules (Qt Components being one such module) and be extended via plugins. This article focuses on how components can be used to write clean and maintainable QML code.
Creating new components
The first example has shown that creating additional components is very easy, so don’t be afraid of making use of that feature.
Don’t only aim for reusability. Aim for encapsulation of implementation details and decoupling of components. Keep components small. Reusability then comes naturally.
Let’s take a look at a simple analog clock example:
download example / view online
main.qml:

import QtQuick 1.0
// Shows current time in analog clock.
Rectangle {
    id: root
    width:  320
    height: 320
    property variant now: new Date()
    Timer {
        id: clockUpdater
        interval: 1000 // update clock every second
        running: true
        repeat: true
        onTriggered: {
            root.now = new Date()
        }
    }
    Clock {
        id: clock
        anchors.centerIn: parent
        hours: root.now.getHours()
        minutes: root.now.getMinutes()
        seconds: root.now.getSeconds()
    }
}

Clock.qml:

import QtQuick 1.0
// An analog clock that shows hours, minutes and seconds.
Rectangle {
    id: root
    width: 262 // acts as minimum width
    height: 262 // acts as minimum height
    // public:
    property int hours:   0
    property int minutes: 0
    property int seconds: 0
    // private:
    Item {
        id: impl
        Image {
            id: face
            source: "images/face.png"
            Image {
                id: shorthand
                source: "images/shorthand.png"
                smooth: true
                rotation: root.hours * 30
            }
            Image {
                id: longhand
                source: "images/longhand.png"
                smooth: true
                rotation: root.minutes * 6
            }
            Image {
                id: thinhand
                source: "images/thinhand.png"
                smooth: true
                rotation: root.seconds * 6
            }
            Image {
                id: center
                source: "images/knob.png"
            }
        }
    }
}

It contains a clock component and when run, will look like this. Even though it is only used once in the application, it already made sense to split it out from the main file.
 
Qt-Quick-best-practices-Using-components-1
 
Firstly, it keeps the logic in main.qml easy to understand: A timer that updates the hours, minutes and seconds of a clock component. That’s all that a reader of main.qml needs to understand if she plans to extend the functionality there.
Secondly, our Clock component does not have to care about positioning itself in the main view. Consider a Row element using our Clock component n-times. If Clock were to use ‘anchors.fill: parent’ in its root element then we’d be unable to use instances of it in the Row element: Each Clock instance would consume the full width_row, instead of the intended width_row / n. That is also why QML disallows most anchors for elements contained within a Row element. If we want to keep our Clock component reusable, then we cannot make too many assumptions about its usage. Summarized, the root element of a component should neither set anchors to its parent nor should it enforce specific layouts.
The Clock component sets a fixed width and height, though. From a semantical standpoint, it is the default size of our component. It does not restrict consumers of our component, as they can easily change the size on demand. Components with an empty size are treated as invisible element, so having a non-empty default size can avoid silly bugs.
There are other, less obvious benefits, such as the creation of composite elements and the encapsulation of implementation details.
Composing an element – let’s name it ComposedElement – out of simple elements ElementA, ElementB and ElementC makes it easier to add new behaviour. We can add new elements ElementD and ElementE to ComposedElement, without having to change ElementA, ElementB or ElementC. Our simple elements are isolated from each other and thus, will not break easily if one of them changes.
Components can be split into a public and a private part, just as it is known from Java and C++ classes. The public API of a component is the sum of all properties and functions defined in the root item (including inherited properties), meaning that those can be called and controlled from consumers of the component.
Any property or function defined in a nested item (that is, not in the root item) can be seen as entirely private API. This allows encapsulation of implementation details and should become a natural habit when creating components.
To prove the usefulness of encapsulation, we can remove the inner Item element named ‘impl’ from Clock.qml and run the application again (for example, via ‘$ qmlviewer main.qml’). It will show no errors, as the public API of the Clock component was left untouched. This means we are free to change the details of the ‘impl’ item, without any side effect for other components.
We can even extend this idea and let Clock.qml load a specific ‘impl’ element dynamically, depending on the situation. This would introduce the concept of polymorphism to QML, but it is left as an exercise for the reader.
Eventually, having a well designed, minimal public API for each component allows to program to interfaces, not to implementations.
Reusing components
Now let’s see whether Clock.qml is indeed a reusable component. It should be easy to use it for a world clock. In that case, we do not want to show the seconds of the clock. As it turns out, we can slightly modify the behaviour of Clock.qml to not show hours, minutes or seconds whenever their parameter is less than zero. For the Image element with id = thinhand, we can use a new property binding like so:

visible: root.seconds > -1

We have not changed the public API. We also assume that using an analog clock with a negative value for its seconds property didn’t make sense in the first place. That’s why we are reasonably sure that this change will not break the functionality for any existing consumer of our Clock component.
For our world clock example, we might also want to show local weather information. We can use one of the available weather API’s in combination with XmlListModel, which provides a declarative way of data retrieval. The timer that previously only updated the clock’s time will now be used to also refresh the weather data once per hour. Note how a refresh signal was introduced, which is connected to the XmlListModel’s reload function.
The modified example displays world clocks and local weather information for three cities.
Qt-Quick-best-practices-Using-components-560-2
The Clock component was trivial to integrate. We disabled the seconds and used UTC hours, together with an offset for the current city. This works because Clock was designed as a simple view. Had the timer been inside the component – instead of exposing hours, minutes and seconds as properties – this could have been more difficult. Sadly, main.qml has grown considerably. The Repeater element adds to the complexity, together with the cities and utcOffsets arrays.
download example / view online
main.qml:

Rectangle {
    ...
    property variant cities: ["Berlin", "Helsinki", "San Francisco"]
    property variant utcOffsets: [1, 2, -8]
    property variant now: new Date()
    signal refresh()
    Timer {
        ...
        property int hours
        onTriggered: {
            hours = root.now.getHours()
            root.now = new Date()
            // Fetch new weather data every hour:
            if (hours != root.now.getHours()) {
                root.refresh()
            }
        }
    }
    Row {
        anchors.horizontalCenter: parent.horizontalCenter
        Repeater {
            model: root.cities
            // Shows an analog clock with local time and local weather info for a given city.
            Rectangle {
                id: current
                width: 262 // acts as minimum width
                height: 320 // acts as minimum height
                property string city: cities[index]
                property int utcOffset: utcOffsets[index]
                XmlListModel {
                    id: cityQuery
                    ...
                }
                ListView {
                    model: cityQuery
                    anchors.fill: parent
                    delegate:
                        Item {
                        Clock {
                            id: clock
                            anchors.left: parent.left
                            anchors.top: parent.top
                            // Make sure that UTC offset never turns hours negative,
                            // otherwise hours might not be shown:
                            hours: root.now.getUTCHours() + current.utcOffset + 24
                            minutes: root.now.getMinutes()
                            seconds: -1
                        }
                        Row {
                            ...
                            Image {
                                id: icon
                                source: "http://www.google.com" + model.iconUrl
                            }
                            Text {
                                id: label
                                text: current.city + ", "+ model.temperature
                                      + "°Cn" + model.humidity
                                      + "n" + model.windCondition
                            }
                        }
                    }
                }
            }
        }
    }
}

Our aim is to keep main.qml easy to understand. At the same time we do not want to add more complexity to Clock.qml, because then it might be impossible to still use it as a simple analog clock. That’s why we create a new component that is composed of the retrieved weather data and the Clock. It contains the timer, the refresh logic, the XmlListModel and the Clock integration. Instead of maintaining the cities and utcOffsets arrays, we add new public properties to the WeatherWorldClock component:

// public:
property string city: ""
property int utcOffset: 0

We can remove the arrays and the Repeater in main.qml.
download example / view online
main.qml:

import QtQuick 1.0
// Shows world time and current weather conditions for selected cities.
Rectangle {
    id: root
    width:  786
    height: 320
    Row {
        id: cities
        anchors.fill: parent
        anchors.horizontalCenter: parent.horizontalCenter
        WeatherWorldClock {
            city: "Berlin"
            utcOffset: 1
        }
        WeatherWorldClock {
            city: "Helsinki"
            utcOffset: 2
        }
        WeatherWorldClock {
            city: "San Francisco"
            utcOffset: -8
        }
    }
}

The WeatherWorldClock component is isolated from changes in main.qml. We can extend and fix features in either file without having to worry about the other. Should WeatherWorldClock become too complex, we can split it up into more components. Most importantly though, the main logic of our application looks almost trivial now: We initialize WeatherWorldClock components and specify city and UTC offset, and that’s it!
 
Source Nokia Developer

About ReadWrite’s Editorial Process

The ReadWrite Editorial policy involves closely monitoring the tech industry for major developments, new product launches, AI breakthroughs, video game releases and other newsworthy events. Editors assign relevant stories to staff writers or freelance contributors with expertise in each particular topic area. Before publication, articles go through a rigorous round of editing for accuracy, clarity, and to ensure adherence to ReadWrite's style guidelines.

Get the biggest tech headlines of the day delivered to your inbox

    By signing up, you agree to our Terms and Privacy Policy. Unsubscribe anytime.

    Tech News

    Explore the latest in tech with our Tech News. We cut through the noise for concise, relevant updates, keeping you informed about the rapidly evolving tech landscape with curated content that separates signal from noise.

    In-Depth Tech Stories

    Explore tech impact in In-Depth Stories. Narrative data journalism offers comprehensive analyses, revealing stories behind data. Understand industry trends for a deeper perspective on tech's intricate relationships with society.

    Expert Reviews

    Empower decisions with Expert Reviews, merging industry expertise and insightful analysis. Delve into tech intricacies, get the best deals, and stay ahead with our trustworthy guide to navigating the ever-changing tech market.