Create the Native Menu Bar of Mac Apps with Flutter

Since Flutter 3.0, Flutter has supported macOS Desktop Apps Development officially.

I became interested in how Flutter could be used to create typical features of macOS apps. For example, one of the distinct macOS app characteristics is the menu bar.

I looked into the Flutter repository issues on GitHub and checked the release notes. I found the pull request below.

The Flutter 3.0 release notes say the following.

Implements a PlatformMenuBar widget and associated data structures by @gspencergoog in https://github.com/flutter/flutter/pull/100274

From the Flutter 3.0 release notes

I noticed that Flutter could create a native Mac menu bar. So I made the sample code to see the details. The sample code is on GitHub.

TOC

Classes for the Menu Bar

The classes for the Menu Bar are the following:

  • PlatformMenuBar
  • PlatformMenu
  • PlatformMenuItemGroup
  • PlatformMenuItem

You can create the typical macOS menu bar with Flutter as the following screen capture.

Native macOS menu bar.
Native macOS menu bar

Create the menu bar

Use the PlatformMenuBar class to create the menu bar. The PlatformMenuBar class is a widget inherited from the StatefulWidget class. I made the sample code for the menu bar with the PlatformMenuBar class. At this time, the menu bar is empty.

class _MyHomePageState extends State<MyHomePage> {
    @override
    Widget build(BuildContext context) {
        return PlatformMenuBar(
            menus: <MenuItem> [
            ],
            body: Center(
// 省略
            ),
        );
    }
}

Create the menus

Each of the menus, such as “File” menu, “Edit” menu, etc, are instances of the PlatformMenu class. Therefore, you put them into the array and pass it to the menus parameter of the PlatformMenuBar class constructor.

return PlatformMenuBar(
  menus: <MenuItem>[
    PlatformMenu(/*省略*/), // Application Menu
    PlatformMenu(/*省略*/), // File Menu
    PlatformMenu(/*省略*/), // Edit Menu
    PlatformMenu(/*省略*/), // View Menu
    PlatformMenu(/*省略*/), // Window Menu
  ],
  body: Center(
  ),
);

The Application menu

There are two standard menus that every macOS apps have:

  • the Apple menu
  • the Application menu

The macOS manage the Apple menu. Unfortunately, the application can’t modify its items.

The Application menu, situated next to the Apple menu, has the foreground application’s name as its title.

The menus’ order corresponds to the array’s order passed into the PlatformMenuBar class constructor. The first item of the array is an application menu.

The menu title and items

You specify the menu title to the label parameter and items to the menus parameter.

For example, the following code creates the File menu with only the item New.

PlatformMenu(
  label: 'File',
  menus: <MenuItem>[
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(
          label: 'New',
        ),
      ],
    ),
  ],
),

The general macOS App menu structure

The menu structure of a typical Mac application looks like this.

  • Apple Menu
  • Application Menu
  • File Menu
  • Edit Menu
  • View Menu
  • App own menus
  • Window Menu
  • Help Menu

However, it does not necessarily mean that the menu structure has to be exactly like this, so Flutter doesn’t create menus automatically.

When you don’t manually create the menus, the Apple and Application menus are generated automatically. However, Flutter doesn’t automatically generate the items in the Application menu.

The Human Interface Guidelines explain the general menu structure.

Create the separators

In AppKit, menu items and separators are instances of the NSMenuItem class. Therefore, AppKit treats the separator as a menu item.

It is different in the Flutter. The separator divides the items within the menu into distinct groups. So, for example, the menu has two separators, and it is considered as three groups.

  • Item 1
  • Item 2
  • Separator
  • Item 3
  • Item 4
  • Separator
  • Item 5

You can use PlatformMenuItemGroup class to create the group. A code for the above example is the following.

PlatformMenu(
  label: 'MyMenu',
  menus: <MenuItem>[
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(/*省略*/), // Item 1
        PlatformMenuItem(/*省略*/), // Item 2
      ],
    ),
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(/*省略*/), // Item 3
        PlatformMenuItem(/*省略*/), // Item 4
      ],
    ),
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(/*省略*/), // Item 5
      ],
    ),
  ],
),

The separator appears as a group delimiter.

Create the menu item

You can utilize the PlatformMenuItem class to create individual menu items.

PlatformMenuItem(
  label: 'Item Title',
),

The label parameter represents the title of an item.

Implement the action when the item selected

You can assign an action to be executed upon item selection using the onSelected parameter.

PlatformMenuItem(
  label: 'Increment',
  onSelected: () {
    // メニューが選択されたときの処理
    _incrementCounter();
  },
),

In this example, the _incrementCounter() method is executed when the Increment menu item is selected.

Assign shortcut keys

You can specify the shortcut to the shortcut parameter. The shortcut is a SingleActivator class instance.

PlatformMenuItem(
  label: 'Increment',
  shortcut: const SingleActivator(LogicalKeyboardKey.keyI, meta: true),
  onSelected: () {
    _incrementCounter();
  },
),

This code assigns the Command and I keys as the shortcuts.

LogicalKeyboardKey class defines keys you can specify with the modifier key. Then, you pass the key and modifier key to the argument of the SingleActivator class constructor. The following arguments are available, including those omitted.

ParamterModifier Key
controlControl Key
shiftShift Key
altOption Key
metaCommand Key
SingleActivator constructor parameters

Platform-specific menu items

You can utilize PlatformProvidedMenuItem class to create the platform-specific menu items such as the Services and Show All in the application menu. The application couldn’t make them.

For example, the code to create the Services menu item is the following.

PlatformMenuItemGroup(
  members: <MenuItem>[
    if (PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.servicesSubmenu))
      const PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.servicesSubmenu),
  ],
),

The hasMenu() method returns whether the menu item specified in the argument is supported.

You can specify the menu item you want to create to the type argument of the `PlatformProvidedMenuItem` constructor. Flutter defines following macOS-specific menus at the writing time.

  • about
  • quit
  • servicesSubmenu
  • hide
  • hideOtherApplications
  • showAllApplications
  • startSpeaking
  • stopSpeaking
  • toggleFullScreen
  • minimizeWindow
  • zoomWindow
  • arrangeWindowsInFront

Flutter 3.0 defines only macOS-specific menu items, not for other platforms.

Sample Code

I uploaded the sample code to GitHub. Please see it for the actual code.

Let's share this post !
TOC