Sudhanshu Kumar Yadav
Posted on May 16, 2023
Hello, fellow Flutter enthusiasts! 🎉
Today, we're going to dive into something truly magical. We're going to take a Flutter app and make it run in the macOS status bar. Yes, you read that right! We're going to take our Flutter prowess to the next level and make our apps even more accessible and user-friendly. So, buckle up, and let's get started! 🚀
Flutter Meets macOS Status Bar
First things first, let's talk about why you might want to run your Flutter app in the macOS status bar. The status bar is that nifty little area at the top of your screen that houses useful information and quick access to system functions. It's always visible, making it a prime real estate for apps that users need to access frequently.
Imagine having a mini weather app, a quick note-taking app, or a system monitor right there in the status bar. Sounds cool, right? Well, with Flutter, we can make it happen!
The Magic Begins
To get started, we need to set up our Flutter project. If you're new to Flutter, check out the official Flutter installation guide.
flutter create flutter_statusbar_app
cd flutter_statusbar_app
Now, we're going to dig a lil deeper in our brewed flutter project, head to flutter_statusbar_app/macos/Runner/AppDelegate.swift
file. You need to change it a bit with the below code:
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
// Instance of the status bar controller
var statusBarController: StatusBarController?
// Instance of the popover that will display the Flutter UI
var flutterUIPopover = NSPopover.init()
// Initializer for the AppDelegate class
override init() {
// Set the popover behavior to transient, meaning it will close when the user clicks outside of it
flutterUIPopover.behavior = NSPopover.Behavior.transient
}
// Function to determine whether the application should terminate when the last window is closed
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
// Return false to keep the application running even if all windows are closed
return false
}
// Function called when the application has finished launching
override func applicationDidFinishLaunching(_ aNotification: Notification) {
// Get the FlutterViewController from the main Flutter window
let flutterViewController: FlutterViewController =
mainFlutterWindow?.contentViewController as! FlutterViewController
// Set the size of the popover
flutterUIPopover.contentSize = NSSize(width: 360, height: 360) // Change this to your desired size
// Set the content view controller for the popover to the FlutterViewController
flutterUIPopover.contentViewController = flutterViewController
// Initialize the status bar controller with the popover
statusBarController = StatusBarController.init(flutterUIPopover)
// Close the default Flutter window as the Flutter UI will be displayed in the popover
mainFlutterWindow.close()
// Call the superclass's applicationDidFinishLaunching function to perform any additional setup
super.applicationDidFinishLaunching(aNotification)
}
}
Let's break it down:
Import Statements: The import Cocoa and import FlutterMacOS statements are importing the necessary libraries for the macOS application and Flutter.
AppDelegate Class: The AppDelegate class is a subclass of FlutterAppDelegate. This class is the entry point of the application and handles application-level events.
Variables: The statusBar variable is an instance of StatusBarController (not shown in this code snippet), which presumably manages the status bar item. The popover variable is an instance of NSPopover, which is a macOS UI component that displays content in a separate hovering window.
init() Function: The init() function is the initializer for the AppDelegate class. Here, it sets the popover behavior to NSPopover.Behavior.transient, which means the popover will close when the user clicks outside of it.
applicationShouldTerminateAfterLastWindowClosed(_:) Function: This function determines whether the application should terminate when the last window is closed. It returns false, meaning the application will keep running even if all windows are closed.
applicationDidFinishLaunching(_:) Function: This function is called when the application has finished launching. It does several things:
- It gets the FlutterViewController from the mainFlutterWindow, which is the main window of the Flutter application.
- It sets the size of the popover and assigns its contentViewController to the FlutterViewController. This means the Flutter UI will be displayed in the popover.
- It initializes the statusBar with the popover, presumably creating a status bar item that shows the popover when clicked.
- It closes the mainFlutterWindow, as the Flutter UI will be displayed in the popover instead of the main window.
- It calls super.applicationDidFinishLaunching(aNotification), which calls the same function in the superclass (FlutterAppDelegate), performing any additional necessary setup.
Now let's move to creating the StatusBarController.swift
file:
For this open your macos
folder in Xcode. Goto file at the top: File -> New -> File -> Choose Swift File
N.B.: don't try to use shortcut and create this file from other code editor like VSCode.
add the following magical code in the created file
import AppKit
class StatusBarController {
// Instance of the status bar
private var appStatusBar: NSStatusBar
// Instance of the status bar item
private var statusBarMenuItem: NSStatusItem
// Instance of the popover that will display the Flutter UI
private var flutterUIPopover: NSPopover
// Initializer for the StatusBarController class
init(_ popover: NSPopover) {
self.flutterUIPopover = popover
appStatusBar = NSStatusBar.init()
statusBarMenuItem = appStatusBar.statusItem(withLength: 28.0)
// Configure the status bar item's button
if let statusBarMenuButton = statusBarMenuItem.button {
// Set the button's image
statusBarMenuButton.image = #imageLiteral(resourceName: "AppIcon") // Change this to your desired image
statusBarMenuButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarMenuButton.image?.isTemplate = true
// Set the button's action to toggle the popover when clicked
statusBarMenuButton.action = #selector(togglePopover(sender:))
statusBarMenuButton.target = self
}
}
// Function to toggle the popover when the status bar item's button is clicked
@objc func togglePopover(sender: AnyObject) {
if(flutterUIPopover.isShown) {
hidePopover(sender)
}
else {
showPopover(sender)
}
}
// Function to show the popover
func showPopover(_ sender: AnyObject) {
if let statusBarMenuButton = statusBarMenuItem.button {
flutterUIPopover.show(relativeTo: statusBarMenuButton.bounds, of: statusBarMenuButton, preferredEdge: NSRectEdge.maxY)
}
}
// Function to hide the popover
func hidePopover(_ sender: AnyObject) {
flutterUIPopover.performClose(sender)
}
}
Don't worry, we will break down this code blob too if it doesn't look familiar:
This Swift code defines a class StatusBarController
that manages a status bar item for a macOS application. The status bar item is associated with a popover, which is a UI element that displays content in a separate window that appears above other content onscreen.
Import Statement:
import AppKit
imports the necessary library for working with the macOS user interface.Class Definition:
class StatusBarController
defines the class. It has four properties:statusBar
,statusItem
,popover
, andstatusBarButton
.Initializer: The
init(_ popover: NSPopover)
function is the initializer for the class. It takes anNSPopover
as an argument and sets up the status bar and status bar item. The status bar item's button is configured with an image and an action that toggles the popover when clicked.togglePopover(sender: AnyObject) Function: This function is called when the status bar item's button is clicked. It checks if the popover is shown and calls either
showPopover(_:)
orhidePopover(_:)
accordingly.showPopover(_ sender: AnyObject) Function: This function shows the popover. It positions the popover relative to the status bar item's button.
hidePopover(_ sender: AnyObject) Function: This function hides the popover.
In summary, this class manages a status bar item that, when clicked, toggles a popover. The popover is positioned relative to the status bar item's button.
That's all for the native side, moving to dart side 🎯
Run your main.dart
as macOS desktop target selected using;
flutter run -d macos
if you can't see macOS as an option in your available device list;
run this command once
flutter config --enable-macos-desktop
And voila! You've just created a Flutter app that runs in the macOS status bar! 🎉
Wrapping Up
Running a Flutter app in the macOS status bar opens up a world of possibilities. It allows you to create apps that are always accessible and provide real-time information or quick functionality. So go ahead, experiment, and create something amazing!
Posted on May 16, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.