Saving the current state of your Ubuntu SDK app, with no effort

Earlier today I gave an example of how to use U1DB to save the state of your Ubuntu app. Now, U1DB is useful for actually storing actual data, right enough. But if you want to save state — which tab was showing, what was being typed into a text field, what was chosen in a dropdown, where a list was scrolled to — then that’s built right in1 to the Ubuntu SDK.

It’s called StateSaver.

This isn’t used to store data, long term. It’s used to make your app automatically remember what position things were in. So quitting the app and restarting it, or switching to another and then switching back, means the app doesn’t reset to the front screen, doesn’t forget what you were halfway through typing in, doesn’t forget where you’d scrolled to.

To use it, just add StateSaver.properties to the Item you want to save things for. So, for example, if you want your ListView to remember the position it was scrolled to, do this:

ListView {
  id: mylistview
  model: someModel
  delegate: ListItem.Standard { text: "row " + model.index }
  StateSaver.properties: "contentY"
}

Just that one thing. Now your ListView remembers where it was scrolled to after a restart. You can do the same with a set of Tabs (just add StateSaver.properties: "selectedTabIndex") or a TextField (StateSaver.properties: "text").

NB: this isn’t really for data saving. It might, or might not, be appropriate to use it for whether a switch is flipped or not. That’s a setting; when the switch is changed, you ought to be doing something with that information. Ideally you could drive everything, declaratively, off of whether the switch.checked is true, and if you can do that then StateSaver is the ideal place to have that info. But if you run a function when a switch is changed, then don’t use StateSaver to remember its state: use U1DB, and store the other stuff that changed alongside it. It’s about saving state, hence the name.

Saving the state of your app like this is an idea so good that other ideas gather at its feet to pray. I think that this should be turned on automatically for the “relevant” properties of each type of Ubuntu SDK widget, and if for some reason you don’t like it you can turn it off. Not for every single property, but for the ones where state makes sense: the scroll position for a ListView, the entered text for a TextField, the visible tab for Tabs. Small things like this are what make the difference between great apps and merely good ones. Any one individual app isn’t particularly harmed by not remembering this stuff; so many apps do not, on other platforms, that people are used to the idea of having their state thrown away. But if almost all the apps do do this, then the ones that don’t get called out on it and then it gets fixed, and that makes the whole platform better. It’s really important that we create a culture of desire for finished, polished apps for Ubuntu. If your app throws away where I was scrolled to, I want the developer to feel a bit embarrassed about that and immediately go to fix it. That’s what will make our platform feel consistent, feel tight, feel together, feel fun to use; the culture of pushing back on unfinished and unpolished and half-ready apps. Open source has historically not had that culture, but I’d really like to use a platform which does.

Importantly, though, for it to be reasonable for we users to require this of app developers, it has to not be really hard for app developers to do. This, taking complicated things and making them easy for app developers to implement, is what the platform is for. And StateSaver is a great example of it; the platform provides! I’m really impressed that this exists and is part of the SDK. (I’d be even more impressed if it did it automatically, as noted.) Good work, Ubuntu SDK team. This stuff needs more publicity!

Longer code example, which remembers which tab you were looking at, where the list is scrolled to, and what was typed in the text field:

import QtQuick 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1 as ListItem

MainView {
    id: root
    width: units.gu(40)
    height: units.gu(70)

    Tabs {
        id: tabs
        StateSaver.properties: "selectedTabIndex"

        Tab {
            id: t1
            title: "StateSaver, 1"

            page: Page {
                id: pg
                ListView {
                    id: lv
                    model: 40
                    clip: true
                    anchors.fill: parent
                    StateSaver.properties: "contentY"
                    delegate: ListItem.Standard {
                        text: "This is row " + model.index + ". Scroll to wherever."
                    }
                }
            }
        }

        Tab {
            id: t2
            title: "StateSaver, 2"

            Column {
                width: parent.width
                id: col2
                spacing: units.gu(1)
                anchors.centerIn: parent

                Label {
                    text: "Enter your favourite pie"
                    horizontalAlignment: Text.AlignHCenter
                    anchors.horizontalCenter: parent.horizontalCenter
                }
                TextField {
                    id: tf
                    width: parent.width - units.gu(2)
                    StateSaver.properties: "text"
                    anchors.horizontalCenter: parent.horizontalCenter
                }
            }
        }
    }
}
  1. how did I not know this existed! Big thanks to Florian for cueing me in