DEV Community

Igor Montagner
Igor Montagner

Posted on • Edited on • Originally published at igordsm.github.io

Vala reactive programming

I've been doing some web programming using preact these last weeks and one of the things that impressed the most was the way logic and UI are split in code. When doing functional components this is specially explicit. Your function returns whatever should be displayed by the component and receives a props object containing all the information the components need to render. A component rerenders when some of its props are changed, either because an external component made changes or in response to an event.

IMHO, this really helps to organize code and separate concerns. All the logic is written in terms of the components' properties. The GUI is updated in response to changes in the properties in order to synchronize the component state with what is shown on screen.

We can also achieve this in Vala using properties and bindings from GLib. In this article we will produce a login screen that validates the username and password fields. All code is available at Github.

GUI form that validates input

Important: The repo contains many iterations of the code presented in this article. A comment containing the name of the original file is shown at the top of each code listing.

Let's start with the basic code to build the form above. This is just standard GTK code: all the ui elements are created in the construct method. The original file (v1/main.vala) also contains a Gtk.Application subclass that handles initialization and showing our form.

Vala tip: the construct block runs during an object's creation and after named constructors are called.

// excerpt from v1.vala

class LoginForm : Gtk.Grid {
    // ......

    construct {
        width_request = 300;
        margin = 10;
        column_homogeneous = false;
        expand = false;
        valign = Gtk.Align.CENTER;
        halign = Gtk.Align.CENTER;

        var username_field = new Gtk.Entry () {
            hexpand = true,
            margin = 4
        };
        var password_field = new Gtk.Entry () {
            hexpand = true,
            margin = 4
        };
        var validation_warning = new Gtk.Label ("") {
            wrap = true,
            height_request = 50
        };
        var login_btn = new Gtk.Button.with_label ("Login") {
            expand = false,
            halign = Gtk.Align.END
        };
        attach (new Gtk.Label ("Username"), 0, 0);
        attach (username_field, 1, 0);
        attach (password_field, 1, 1);
        attach (new Gtk.Label ("Password"), 0, 1);
        attach (validation_warning, 1, 2);
        attach (login_btn, 1, 3);

        show_all ();
    }
}
Enter fullscreen mode Exit fullscreen mode

There's no interactivity in this form yet. In fact, it already starts in an inconsistent state: the Login button is enabled even though block fields are empty. We could fix this simply by setting sensitive=false in login_btn initialization, but we won't. Instead we will create a property called is_valid and bind its value to login_btn.sensitive. Every time one of the values change the other is instantly updated. Bindings can also be created with many values as long as a cicle is not introduced.

Let's use this now in our program: first we create a property is_valid and then we use bind_property to bind its value to login_btn.sensitive. Our binding flags indicate that the binding is bidirectional and that the value is synchronized when the binding is created.

// excerpt from v2.vala

class LoginForm : Gtk.Grid {
    public bool is_valid { get; set; default = false; }

    construct {
        // GUI creation code

        bind_property ("is_valid", login_btn, "sensitive", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
    }
Enter fullscreen mode Exit fullscreen mode

Now the login button is disabled when the app starts and will remain so until we set is_valid=true. Unfortunately this never happens, so let's add a property username and bind it to username_field.text. When username is set we check if its length is larger than 0 and set is_valid accordingly.

// excerpt from v3.vala

class LoginForm : Gtk.Grid {
    public bool is_valid { get; set; default = false; }

    private string _username = "";
    public string username { get {
        return _username;
    }
    set {
        _username = value;
        if (value.length > 0) {
            is_valid = true;
        }
        else {
            is_valid = false;
        }
    }}

    construct {
        // GUI creation code

        bind_property ("is_valid", login_btn, "sensitive", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
        bind_property ("username", username_field, "text", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
    }
Enter fullscreen mode Exit fullscreen mode

Run the app and test the new validation: the login button should be disable when the app starts and respond to is_valid. IMHO, the impressive part of this code is that all GUI code is on construct and our logic is written only in terms of properties. When we update the Widget's properties the UI will update in response, so our application state is always consistent.

Now the final step is to set validation_error to a meaningful message. We can do this with another property binding. To make code more readble we also refactor all our validation code into a function void validate_form ().

// excerpt from v4.vala

class LoginForm : Gtk.Grid {
    public string validation_error { get; set; default = ""; }
    public string username { get {
        return _username;
    }
    set {
        _username = value;
        validate_form ();
    } }

    //....

    construct {
        // ....

        bind_property ("validation_error", validation_warning, "label", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
    }

    private void validate_form () {
        if (_username.length == 0) {
            is_valid = false;
            validation_error = "Empty username";
            return;
        }
        is_valid = true;
        validation_error = "";
    }
Enter fullscreen mode Exit fullscreen mode

And that's it for the username field. We can do exactly the same for the password field and arrive at the final code available at final.vala in the example repository.

Another interesting project is Blueprint compiler, which creates a sort of DSL for Gtk components. So, what do you think about this style of Gtk programming? Join the conversation in the comments ;)

Top comments (0)