starter template for building menubar app in flutter
Use multi_windows branch for example with addtional windows (select include all branches when using this template)
use this template or follow the following step in your flutter project:
- Change to Content of macos/Runner/AppDelegate.swift
import Cocoa
import FlutterMacOS
import os.log
@main
class AppDelegate: FlutterAppDelegate {
var statusBar: StatusBarController?
var popover = NSPopover.init()
private enum Popover {
static let width: CGFloat = 360
static let height: CGFloat = 360
//change this to your desired size
}
override init() {
popover.behavior = NSPopover.Behavior.transient //to make the popover hide when the user clicks outside of it
}
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return false
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
override func applicationDidFinishLaunching(_ aNotification: Notification) {
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
fatalError("bundleIdentifier must not be nil")
}
let newFlutterEngine = FlutterEngine(name: bundleIdentifier, project: nil)
newFlutterEngine.run(withEntrypoint: nil)
let controller = FlutterViewController(engine: newFlutterEngine, nibName: nil, bundle: nil)
RegisterGeneratedPlugins(registry: newFlutterEngine)
let popoverController = PopoverContentController(flutterViewController: controller)
popover.contentSize = NSSize(width: Popover.width, height: Popover.height)
popover.contentViewController = popoverController
statusBar = StatusBarController.init(popover)
guard let window = mainFlutterWindow else {
os_log("mainFlutterWindow is nil", type: .error)
return
}
window.close()
super.applicationDidFinishLaunching(aNotification)
}
}- Add a new Swift file named 'PopoverContentController.swift' in macos/Runner
import Cocoa
import FlutterMacOS
class PopoverContentController: NSViewController {
private let flutterViewController: FlutterViewController
init(flutterViewController: FlutterViewController) {
self.flutterViewController = flutterViewController
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable, message: "Loading from a nib is not supported")
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
self.view = NSView()
}
override func viewDidLoad() {
super.viewDidLoad()
self.addChild(flutterViewController)
view.addSubview(flutterViewController.view)
flutterViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
flutterViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
flutterViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
flutterViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
flutterViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
override func viewDidAppear() {
super.viewDidAppear()
// Ensure the window becomes key and the app is active when the popover appears
self.view.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
}- Add a new Swift file named 'StatusBarController.swift' in macos/Runner
Add the following code to the StatusBarController.swift
import AppKit
class StatusBarController {
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
init(_ popover: NSPopover) {
self.popover = popover
statusBar = NSStatusBar.system
statusItem = statusBar.statusItem(withLength: 28.0)
if let statusBarButton = statusItem.button {
statusBarButton.image = #imageLiteral(resourceName: "AppIcon") //change this to your desired image
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
}
}
@objc func togglePopover(sender: AnyObject) {
if(popover.isShown) {
hidePopover(sender)
}
else {
showPopover(sender)
}
}
func showPopover(_ sender: AnyObject) {
if let statusBarButton = statusItem.button {
popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
}
}
func hidePopover(_ sender: AnyObject) {
popover.performClose(sender)
}
}- Add the following to macos/Runner/Info.plist to hide the dock icon and application menu
<key>LSUIElement</key>
<true/>
To close the app programmatically, use the following code
exit(0)
This project is a starting point for a Flutter menubar application in macOS.
A few resources to get you started if this is your first Flutter project:
For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.


