Settings

We have now learned multiple ways to handle states. However, every time we close the application all of it is gone. Let us learn how to use GSettings by storing the state of a Switch in it.

At the very beginning we have to create a GSchema xml file in order to describe the kind of data our application plans to store in the settings.

Filename: listings/settings/1/org.gtk.example.gschema.xml

<?xml version="1.0" encoding="utf-8"?>
<schemalist>
  <schema id="org.gtk.example" path="/org/gtk/example/">
    <key name="is-switch-enabled" type="b">
      <default>false</default>
      <summary>Default switch state</summary>
    </key>
  </schema>
</schemalist>

Let us get through it step by step. The id is the same application id we used when we created our application.

Filename: listings/settings/1/main.rs

use gio::Settings;
use gtk::gio;
use gtk::{glib::signal::Inhibit, prelude::*};
use gtk::{Align, Application, ApplicationWindow, Switch};

fn main() {
    // Create a new application
    let app = Application::new(Some("org.gtk.example"), Default::default());
    app.connect_activate(build_ui);

    // Run the application
    app.run();
}

fn build_ui(application: &Application) {
    // Create a window
    let window = ApplicationWindow::builder()
        .application(application)
        .title("My GTK App")
        .build();

    // Initialize settings
    let settings = Settings::new("org.gtk.example");

    // Get the last switch state from the settings
    let is_switch_enabled = settings.boolean("is-switch-enabled");

    // Create a switch
    let switch = Switch::builder()
        .margin_top(48)
        .margin_bottom(48)
        .margin_start(48)
        .margin_end(48)
        .valign(Align::Center)
        .halign(Align::Center)
        .state(is_switch_enabled)
        .build();

    switch.connect_state_set(move |_, is_enabled| {
        // Save changed switch state in the settings
        settings
            .set_boolean("is-switch-enabled", is_enabled)
            .unwrap();
        // Do not inhibit the the default handler
        Inhibit(false)
    });

    // Add button
    window.set_child(Some(&switch));
    window.present();
}

The path must start and end with a forward slash character ('/') and must not contain two sequential slash characters. When creating a path, we advise to take the id, replace the '.' with '/' and add '/' at the front and end of it.

We only want to store a single key with the name "is-switch-enabled". This is a boolean value so its type is "b" (see GVariant Format Strings for the other options). Finally, we define its default value and add a summary.

Now we have to install the GSchema by executing the following commands:

$ sudo install -D org.gtk.example.gschema.xml /usr/share/glib-2.0/schemas/
$ sudo glib-compile-schemas /usr/share/glib-2.0/schemas/

This has to be repeated every time we change the GSchema. That is why we probably want to use a build system like Meson to do it for us.

We initialize the Settings object by specifying the application id.

Filename: listings/settings/1/main.rs

use gio::Settings;
use gtk::gio;
use gtk::{glib::signal::Inhibit, prelude::*};
use gtk::{Align, Application, ApplicationWindow, Switch};

fn main() {
    // Create a new application
    let app = Application::new(Some("org.gtk.example"), Default::default());
    app.connect_activate(build_ui);

    // Run the application
    app.run();
}

fn build_ui(application: &Application) {
    // Create a window
    let window = ApplicationWindow::builder()
        .application(application)
        .title("My GTK App")
        .build();

    // Initialize settings
    let settings = Settings::new("org.gtk.example");

    // Get the last switch state from the settings
    let is_switch_enabled = settings.boolean("is-switch-enabled");

    // Create a switch
    let switch = Switch::builder()
        .margin_top(48)
        .margin_bottom(48)
        .margin_start(48)
        .margin_end(48)
        .valign(Align::Center)
        .halign(Align::Center)
        .state(is_switch_enabled)
        .build();

    switch.connect_state_set(move |_, is_enabled| {
        // Save changed switch state in the settings
        settings
            .set_boolean("is-switch-enabled", is_enabled)
            .unwrap();
        // Do not inhibit the the default handler
        Inhibit(false)
    });

    // Add button
    window.set_child(Some(&switch));
    window.present();
}

Then we get the settings key and use it when we create our Switch.

Filename: listings/settings/1/main.rs

use gio::Settings;
use gtk::gio;
use gtk::{glib::signal::Inhibit, prelude::*};
use gtk::{Align, Application, ApplicationWindow, Switch};

fn main() {
    // Create a new application
    let app = Application::new(Some("org.gtk.example"), Default::default());
    app.connect_activate(build_ui);

    // Run the application
    app.run();
}

fn build_ui(application: &Application) {
    // Create a window
    let window = ApplicationWindow::builder()
        .application(application)
        .title("My GTK App")
        .build();

    // Initialize settings
    let settings = Settings::new("org.gtk.example");

    // Get the last switch state from the settings
    let is_switch_enabled = settings.boolean("is-switch-enabled");

    // Create a switch
    let switch = Switch::builder()
        .margin_top(48)
        .margin_bottom(48)
        .margin_start(48)
        .margin_end(48)
        .valign(Align::Center)
        .halign(Align::Center)
        .state(is_switch_enabled)
        .build();

    switch.connect_state_set(move |_, is_enabled| {
        // Save changed switch state in the settings
        settings
            .set_boolean("is-switch-enabled", is_enabled)
            .unwrap();
        // Do not inhibit the the default handler
        Inhibit(false)
    });

    // Add button
    window.set_child(Some(&switch));
    window.present();
}

Finally, we assure that the switch state is stored in the settings whenever we click on it.

Filename: listings/settings/1/main.rs

use gio::Settings;
use gtk::gio;
use gtk::{glib::signal::Inhibit, prelude::*};
use gtk::{Align, Application, ApplicationWindow, Switch};

fn main() {
    // Create a new application
    let app = Application::new(Some("org.gtk.example"), Default::default());
    app.connect_activate(build_ui);

    // Run the application
    app.run();
}

fn build_ui(application: &Application) {
    // Create a window
    let window = ApplicationWindow::builder()
        .application(application)
        .title("My GTK App")
        .build();

    // Initialize settings
    let settings = Settings::new("org.gtk.example");

    // Get the last switch state from the settings
    let is_switch_enabled = settings.boolean("is-switch-enabled");

    // Create a switch
    let switch = Switch::builder()
        .margin_top(48)
        .margin_bottom(48)
        .margin_start(48)
        .margin_end(48)
        .valign(Align::Center)
        .halign(Align::Center)
        .state(is_switch_enabled)
        .build();

    switch.connect_state_set(move |_, is_enabled| {
        // Save changed switch state in the settings
        settings
            .set_boolean("is-switch-enabled", is_enabled)
            .unwrap();
        // Do not inhibit the the default handler
        Inhibit(false)
    });

    // Add button
    window.set_child(Some(&switch));
    window.present();
}

The Switch now retains its state even after closing the application. But we can make this even better. The Switch has a property "state" and Settings allows us to bind properties to a specific setting. So let us do exactly that.

We can remove the boolean call before initializing the Switch as well as the connect_state_set call. We then bind the setting to the property by specifying the key, object and name of the property. Additionally, we specify SettingsBindFlags to control the direction in which the binding works.

Filename: listings/settings/2/main.rs

use gio::{Settings, SettingsBindFlags};
use gtk::gio;
use gtk::prelude::*;
use gtk::{Align, Application, ApplicationWindow, Switch};

fn main() {
    // Create a new application
    let app = Application::new(Some("org.gtk.example"), Default::default());
    app.connect_activate(build_ui);

    // Run the application
    app.run();
}

fn build_ui(application: &Application) {
    // Create a window
    let window = ApplicationWindow::builder()
        .application(application)
        .title("My GTK App")
        .build();

    // Initialize settings
    let settings = Settings::new("org.gtk.example");

    // Create a switch
    let switch = Switch::builder()
        .margin_top(48)
        .margin_bottom(48)
        .margin_start(48)
        .margin_end(48)
        .valign(Align::Center)
        .halign(Align::Center)
        .build();

    settings
        .bind("is-switch-enabled", &switch, "state")
        .flags(SettingsBindFlags::DEFAULT)
        .build();

    // Add button
    window.set_child(Some(&switch));
    window.present();
}

Whenever you have a property which nicely correspond to a setting, you probably want to bind it to it. In other cases, interacting with the settings via the getter and setter methods tends to be the right choice.