Skip to content

Instantly share code, notes, and snippets.

@whit3rabbit
Last active July 20, 2025 23:17
Show Gist options
  • Select an option

  • Save whit3rabbit/44053267bf15deade09b24d79d1910b9 to your computer and use it in GitHub Desktop.

Select an option

Save whit3rabbit/44053267bf15deade09b24d79d1910b9 to your computer and use it in GitHub Desktop.
valacheatsheet.md

The Vala with GTK4 Cheat Sheet


1. Installation & Project Setup

Installation

  • Fedora: sudo dnf install vala gtk4-devel
  • Debian/Ubuntu: sudo apt install valac libgtk-4-dev
  • Arch Linux: sudo pacman -S vala gtk4
  • macOS (Homebrew): brew install vala gtk4
  • Windows: Use WSL (Windows Subsystem for Linux) or MSYS2 and follow the Linux instructions.

Verify Installation: valac --version

Project Setup with Meson (Recommended)

Use the Meson build system for any project with more than one file.

1. Create a meson.build file:

project('my-gtk-app', 'vala', 'c')

dependencies = [
  dependency('gtk4'),
  dependency('glib-2.0')
]

executable(
  'my-app',
  'src/main.vala',
  'src/window.vala',
  dependencies: dependencies,
  install: true
)

2. Build your project:

meson setup build
ninja -C build

2. Vala Language Fundamentals (For Newcomers)

Variables & Data Types

Vala is statically typed, with var for convenient type inference.

string name = "John Doe";
int age = 42;
var greeting = "Hello, Vala!"; // Inferred as 'string'

Functions (Methods)

Convention is lower_case_with_underscores.

void print_greeting(string name) {
    stdout.printf("Hello, %s\n", name);
}

int add(int a, int b) { return a + b; }

Control Flow

Standard if-else, for, while, foreach, and switch.

var colors = new string[] {"Red", "Green", "Blue"};
foreach (var color in colors) {
    stdout.printf("Color: %s\n", color);
}

Basic Classes & Objects

Inherit from GLib.Object for automatic memory management, signals, and properties.

public class User : GLib.Object {
    public string username { get; set; }
    public User(string username) { Object(username: username); }
}

3. Core Vala Concepts

Memory Management: Automatic Reference Counting (ARC)

Vala manages memory by counting references to objects. When the count drops to zero, the object is freed.

  • The Danger (Reference Cycles): If object A holds a reference to B, and B holds a reference back to A, they create a memory leak.

  • The Solution (weak): Declare one reference as weak to break the cycle. A weak reference does not keep an object alive.

    public class Page : GLib.Object {
        public weak Document parent_document; // Breaks the cycle
    }

Synchronous Error Handling (throws and try-catch)

If a regular (non-async) method can fail, it must be declared with throws.

public errordomain ConfigError { PARSE_FAILED; }

public void load_config() throws ConfigError {
    throw new ConfigError.PARSE_FAILED("Invalid syntax");
}

try {
    load_config();
} catch (ConfigError e) {
    warning("Error: %s", e.message);
}

4. Mastering Asynchronous Error Handling (yield and try-catch)

This is one of the most powerful but trickiest parts of Vala. Errors in async methods are handled differently.

The Golden Rule of Async Error Handling

To catch an error from an async method, you MUST call it with yield from within another async method. The try-catch block must surround the yield statement.

The Correct Pattern: try-catch around yield

Here is how to correctly handle a potential failure in an asynchronous operation.

// 1. An async method that can fail
public errordomain NetworkError { TIMEOUT; }

public async string fetch_data() throws NetworkError {
    // Simulate a network operation that might fail
    var success = GLib.Random.next_boolean();
    if (success) {
        yield new GLib.Timeout.add_seconds(1, () => { return false; });
        return "Here is your data";
    } else {
        throw new NetworkError.TIMEOUT("The server did not respond.");
    }
}

// 2. A calling method that correctly handles the error
public async void perform_fetch() {
    stdout.printf("Attempting to fetch data...\n");
    try {
        // The 'yield' pauses execution and waits for fetch_data to finish
        string data = yield fetch_data();
        // This code only runs if fetch_data() SUCCEEDS
        stdout.printf("Success! Data: %s\n", data);
    } catch (NetworkError e) {
        // The catch block executes if fetch_data() throws an error
        stderr.printf("Caught an error: %s\n", e.message);
    }
}

The Common Mistake: "Fire and Forget"

If you call an async method without yield, you cannot catch its exceptions. The try-catch block will be useless.

public void bad_error_handling() {
    try {
        // WRONG: This just starts the operation and continues immediately.
        // The error will be thrown later, long after the try-catch block has finished.
        fetch_data();
    } catch (NetworkError e) {
        // THIS CODE WILL NEVER BE REACHED
        stderr.printf("This will never print.\n");
    }
    // The program will likely crash or print a critical error to the console.
}

Chaining Async Calls and Propagating Errors

Errors bubble up through a yield chain, just like a regular call stack.

// This method doesn't handle the error, it just passes it up.
// Note the 'async throws' signature.
public async string get_and_process_data() throws NetworkError {
    // We yield to fetch_data(). If it throws, the error is
    // automatically passed up to whoever called get_and_process_data().
    string raw_data = yield fetch_data();
    return raw_data.up(); // Process the data
}

// The top-level caller can then handle it:
public async void top_level_caller() {
    try {
        string processed = yield get_and_process_data();
        stdout.printf("Processed data: %s\n", processed);
    } catch (NetworkError e) {
        stderr.printf("Error occurred deep in the call chain: %s\n", e.message);
    }
}

Handling Async Errors at the Top Level (e.g., in a Button Click)

Sometimes you must start an async operation from a synchronous context (like a signal handler), where you cannot use yield. In this case, you must use the traditional _async/_finish pattern with a callback.

yield is just syntactic sugar for this pattern.

// In a GTK widget (e.g., a window class)
private void on_my_button_clicked() {
    // The handler itself is not async, so we cannot use 'yield'.
    // We must use a callback to get the result.
    perform_fetch.callback.begin(this, (source, res) => {
        try {
            // The corresponding _finish method re-throws the exception.
            // The try-catch block MUST be here, in the callback.
            perform_fetch.callback.end(res);
        } catch (Error e) {
            // Now we can handle the error properly
            var dialog = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,
                Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
                "An error occurred: %s".printf(e.message));
            dialog.present();
        }
    });
}

5. Modern GTK4 Development Patterns

The Gtk.Application Entry Point

All GTK4 apps must start with a Gtk.Application class.

// src/main.vala
public class MyApplication : Gtk.Application {
    public MyApplication() { Object(application_id: "com.example.MyApp"); }
    protected override void activate() {
        var window = new MyWindow(this);
        window.present();
    }
}

public static int main(string[] args) { return new MyApplication().run(args); }

Building UI with Composite Templates

The standard way to link a UI design file (.ui) to a Vala class.

my_window.ui file:

<interface>
  <template class="MyWindow" parent="GtkApplicationWindow">
    <child>
      <object class="Gtk.Button" id="hello_button">
        <signal name="clicked" handler="on_hello_button_clicked"/>
      </object>
    </child>
  </template>
</interface>

my_window.vala file:

[Gtk.Template(ui = "/com/example/myapp/my_window.ui")]
public class MyWindow : Gtk.ApplicationWindow {
    [Gtk.Child] private Gtk.Button hello_button;

    public MyWindow(Gtk.Application app) { Object(application: app); }

    [Gtk.Callback]
    private void on_hello_button_clicked() { /* ... */ }
}

Dynamic Lists with Gtk.ListView

The efficient, modern way to display lists of data. It separates the data model from the view.

Input Handling with Event Controllers

The modern way to handle mouse and keyboard input.

var gesture = new Gtk.GestureClick();
gesture.pressed.connect((n_press, x, y) => {
    stdout.printf("Widget clicked at (%.1f, %.1f)\n", x, y);
});
my_widget.add_controller(gesture);

Smooth Animation

Use add_tick_callback to sync animations with the screen's refresh rate.

widget.add_tick_callback((_, clock) => {
    widget.queue_draw(); // Request a redraw
    return true; // Return true to continue animating
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment