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.

../../../_images/adding_toasts.png

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 TextViewerWindow

  2. Find the definition for the GtkScrolledWindow that contains the main text area

  3. Insert the AdwToastOverlay widget as the child of the TextViewerWindow and the parent of the GtkScrolledWindow, and use the toast_overlay id

<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

  1. You now must add a new member to the TextViewerWindow instance structure for the toast_overlay widget:

struct _TextViewerWindow
{
  AdwApplicationWindow  parent_instance;

  /* Template widgets */
  AdwHeaderBar *header_bar;
  GtkTextView *main_text_view;
  GtkButton *open_button;
  GtkLabel *cursor_pos;
  AdwToastOverlay *toast_overlay;
};
  1. Bind the newly added toast_overlay widget to the template in the class initialization function text_viewer_window_class_init of the TextViewerWindow type:

static void
text_viewer_window_class_init (TextViewerWindowClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class, "/com/example/TextViewer/text_viewer-window.ui");
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, header_bar);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, main_text_view);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, open_button);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, cursor_pos);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, 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 opening a file

  1. Find the open_file_complete function for TextViewerWindow

  2. Find the error handling blocks and replace them with a toast

  3. Add a toast at the end of the function

static void
open_file_complete (GObject          *source_object,
                    GAsyncResult     *result,
                    gpointer          user_data)
{
  GFile *file = G_FILE (source_object);
  TextViewerWindow *self = user_data;

  g_autofree char *contents = NULL;
  gsize length = 0;

  g_autoptr (GError) error = NULL;

  // Complete the asynchronous operation; this function will either
  // give you the contents of the file as a byte array, or will
  // set the error argument
  g_file_load_contents_finish (file,
                               result,
                               &contents,
                               &length,
                               NULL,
                               &error);

  // Query the display name for the file
  g_autofree char *display_name = NULL;
  g_autoptr (GFileInfo) info =
  g_file_query_info (file,
                     "standard::display-name",
                     G_FILE_QUERY_INFO_NONE,
                     NULL,
                     NULL);
  if (info != NULL)
    {
      display_name =
        g_strdup (g_file_info_get_attribute_string (info, "standard::display-name"));
    }
  else
    {
      display_name = g_file_get_basename (file);
    }

  // In case of error, show a toast
  if (error != NULL)
    {
      g_autofree char *msg =
        g_strdup_printf ("Unable to open “%s”", display_name);

      adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (msg));
      return;
    }

  // Ensure that the file is encoded with UTF-8
  if (!g_utf8_validate (contents, length, NULL))
    {
      g_autofree char *msg =
        g_strdup_printf ("Invalid text encoding for “%s”", display_name);

      adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (msg));
      return;
    }

  // Retrieve the GtkTextBuffer instance that stores the
  // text displayed by the GtkTextView widget
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);

  // Set the text using the contents of the file
  gtk_text_buffer_set_text (buffer, contents, length);

  // Reposition the cursor so it's at the start of the text
  GtkTextIter start;
  gtk_text_buffer_get_start_iter (buffer, &start);
  gtk_text_buffer_place_cursor (buffer, &start);

  // Set the title using the display name
  gtk_window_set_title (GTK_WINDOW (self), display_name);

  // Show a toast for the successful loading
  g_autofree char *msg =
    g_strdup_printf ("Opened “%s”", display_name);

  adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (msg));
}

Notify after saving to a file

  1. In the save_file_complete function you can use a toast to notify the user that the operation succeeded or failed

static void
save_file_complete (GObject          *source_object,
                    GAsyncResult     *result,
                    gpointer          user_data)
{
  GFile *file = G_FILE (source_object);
  TextViewerWindow *self = user_data;

  g_autoptr (GError) error =  NULL;
  g_file_replace_contents_finish (file, result, NULL, &error);

  // Query the display name for the file
  g_autofree char *display_name = NULL;
  g_autoptr (GFileInfo) info =
  g_file_query_info (file,
                     "standard::display-name",
                     G_FILE_QUERY_INFO_NONE,
                     NULL,
                     NULL);
  if (info != NULL)
    {
      display_name =
        g_strdup (g_file_info_get_attribute_string (info, "standard::display-name"));
    }
  else
    {
      display_name = g_file_get_basename (file);
    }

  g_autofree char *msg = NULL;
  if (error != NULL)
    msg = g_strdup_printf ("Unable to save as “%s”", display_name);
  else
    msg = g_strdup_printf ("Saved as “%s”", display_name);

  adw_toast_overlay_add_toast (self->toast_overlay, adw_toast_new (msg));
}

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