Skip to content

Notifying The User With Toasts

Toasts, or “in-app notifications”, are useful to communicate a change in state from within the application, or to gather feedback from the user.

In this lesson, you will learn how to add a toast overlay to the text viewer application, and how to display a toast when opening a file.

Screenshot of text-viewer in light mode with the 'COPYING' file open. A toast is visible at the bottom with the content 'Opened "COPYING"'

Add a toast overlay

Toasts are displayed by an overlay, which must contain the rest of the application’s content area.

Update the UI definition file

  1. Find the UI definition file for Text::Viewer::Window
  2. Find the definition for the GtkScrolledWindow that contains the main text area
  3. Insert the AdwToastOverlay widget as the child of the Text-Viewer-Window and the parent of the GtkScrolledWindow, and use the toast_overlay id
xml
<property name="content">
  <object class="AdwToastOverlay" id="toast_overlay">
    <property name="child">
      <object class="GtkScrolledWindow">
        <property name="hexpand">true</property>
        <property name="vexpand">true</property>
        <property name="margin-top">6</property>
        <property name="margin-bottom">6</property>
        <property name="margin-start">6</property>
        <property name="margin-end">6</property>
        <property name="child">
          <object class="GtkTextView" id="main_text_view">
            <property name="monospace">true</property>
          </object>
        </property>
      </object>
    </property>
  </object>
</property>

Bind the overlay in the source

Add the toast_overlay widget to the Text::Viewer::Window class

crystal
module Text::Viewer
  @[Gtk::UiTemplate(
    resource: "/com/example/TextViewer/text-viewer-window.ui",
    children: {
      "header_bar",
      "main_text_view",
      "open_button",
      "cursor_pos",
      "toast_overlay",
    }
  )]
  class Window < Adw::ApplicationWindow
    include Gtk::WidgetTemplate

    @header_bar = Adw::HeaderBar
    @main_text_view : Gtk::TextView
    @open_button : Gtk::Button
    @cursor_pos : Gtk::Label
    @toast_overlay : Adw::ToastOverlay

    @settings : Gio::Settings

    def initialize
      super()

      @settings = Gio::Settings.new("com.example.TextViewer")

      @header_bar = Adw::HeaderBar.cast(template_child("header_bar"))
      @main_text_view = Gtk::TextView.cast(template_child("main_text_view"))
      @open_button = Gtk::Button.cast(template_child("open_button"))
      @cursor_pos = Gtk::Label.cast(template_child("cursor_pos"))
      @toast_overlay = Adw::ToastOverlay.cast(template_child("toast_overlay"))
      #...

Show toasts

Toasts are especially useful for notifying the user that an asynchronous operation has terminated. Opening a file and saving it are two typical use cases for a notification.

Notify after file operations

  1. Find the open_file method for Text::Viewer::Window
  2. Add a toast at the end of the method
crystal
    private def open_file(file : Gio::File?)
      return if file.nil?

      file_path = file.not_nil!.path.not_nil!
      File.open(file_path) do |file_io|
        self.title = File.basename(file_path, File.extname(file_path))

        # Retrieve the `Gtk::TextBuffer` instance that stores the
        # text displayed by the `Gtk::TextView` widget
        buffer = @main_text_view.buffer

        # Set the text using the contents of the file
        buffer.text = file_io.gets_to_end
        # Reposition the cursor so it's at the start of the text
        buffer.place_cursor(buffer.start_iter)

        # Show a toast for the successful loading
        @toast_overlay.add_toast(Adw::Toast.new("Opened “#{self.title}“"));
      end
    end
  1. Similarly for the save_file method
crystal
    private def save_file(file : Gio::File?)
      return if file.nil?

      file_path = file.not_nil!.path.not_nil!
      File.open(file_path, "w") do |file_io|
        buffer = @main_text_view.buffer

        # Retrieve the iterator at the start of the buffer
        start_iter = buffer.start_iter

        # Retrieve the iterator at the end of the buffer
        end_iter = buffer.end_iter

        # Retrieve all the visible text between the two bounds
        text = buffer.text(start_iter, end_iter, false)
        return if text.size == 0

        file_io.print(text)

        @toast_overlay.add_toast(Adw::Toast.new("Saved as “#{File.basename(file_path, File.extname(file_path))}“"));
      end
    end

In this lesson you learned how to notify the user of a long running operation that either succeeded or failed using toasts.

Released under a Creative Commons Zero v1.0 Universal License