Lazy loading QML

Software

What is one of the most important things while writing a QML UI? the answer is pretty easy: KISS.

There are several reasons: the first is of course a reason that is pretty valid in any UI in general: the more an interface is simple, the less visual elements there are, the less redundancy, the more elegant and easy it looks (attention: it does not mean less features, it means just better design
in their representation)

And second, of course the more objects are on the screen, the more memory is taken for their representation, sometimes a non negligible quantity.

Another thing that is very important is to actually load your interface only for the things that you are actually showing right now on the screen, anything that is hidden, or can be shown just eventually, should be instantiated only just before actually showing, otherwise you are paying in startup time and memory for some objects that may even never be actually shown to the user.

A typical example is a tabbar with maybe 10 pages, if those pages aren’t performing an important background task (like loading an html page) why bother loading them?

I’ve just added a new very simple QML component in org.kde.plasma.extras: ConditionalLoader.

It works pretty much like a standard loader: you specify a source component and it instances it, but just when a certain condition is satisfied, so for instance:

 import QtQuick 1.1
 import org.kde.plasma.components 0.1 as PlasmaComponents
 import org.kde.plasma.extras 0.1 as PlasmaExtras

 Item {
     PlasmaComponents.TabBar {
        PlasmaComponents.TabButton {
             text: "foo"
             tab: fooTab
        }
        PlasmaComponents.TabButton {
             text: "bar"
             tab: barTab
        }
        PlasmaComponents.TabButton {
             text: "baz"
             tab: bazTab
        }
    }

    PlasmaComponents.TabGroup {
        id: tabGroup
        PlasmaExtras.ConditionalLoader {
            id: fooTab
            when: tabGroup.currentTab == fooTab
            //source can be a path name as well
            source: Qt.createComponent("Foo.qml")
        }
        PlasmaExtras.ConditionalLoader {
            id: barTab
            when: tabGroup.currentTab == barTab
            source: Qt.createComponent("Bar.qml")
        }
        PlasmaExtras.ConditionalLoader {
            id: bazTab
            when: tabGroup.currentTab == bazTab
            source: Qt.createComponent("Baz.qml")
        }
    }
 }

In this example (simplified, without anchors and whatnot) the content of the tabs is in separated files, and they will get loaded only when the tab will become the current.

It may be used also to do other stuff, like in PopupApplets to load some things only when the popup gets open for the first time, or in delegates of listviews to load extra ui parts when the user clicks on an item, or whatever many other uses.

5 thoughts on “Lazy loading QML

  1. Ian Monroe

    In QtQuick2 they added a parent parameter to the createComponent command because it’s somewhat of a memory leak otherwise.

    I would just use the Component {} component when possible, though maybe then you lose some of the benefits of holding off parsing until later (though QML parsing is very fast).

    Reply
  2. aleix

    it should accept source files and let you do the management indoors. This will leak, like ian said 😛

    Maybe copy the Loader API a bit further? Anyhow, I really like the idea, I’ve implemented that myself a couple of times already 😛

    Reply
  3. Marco Martin

    as i said, it accepts both components or path names.
    internally it actually has a loader, (so i hope components created by the loader by setting a source path on it won’t leak)

    what i should have said is that path names are preferred, for this reason and for delaying qml parsing more

    Reply
  4. markg85

    Hi Marco,

    (how did i miss this post?) That stuff you made looks neat to use! I’m currently using a “slightly” more complicated version with the Loader and states. Like so:

    Item {
    state: “list”

    states: [
    State {
    name: “icon”
    PropertyChanges { target: viewContainer; source: “views/IconView.qml” }
    },
    State {
    name: “list”
    PropertyChanges { target: viewContainer; source: “views/LView.qml” }
    },
    State {
    name: “tree”
    PropertyChanges { target: viewContainer; source: “views/TreeView.qml” }
    }
    ]

    Loader {
    anchors.fill: parent
    id: viewContainer
    }
    }

    Works equally well, but you just prevented the use of a state 🙂
    Not sure yet which one i prefer though..

    Reply

Comments are closed.