Flutter for Desktop Apps
•11 min read
Flutter's support for desktop platforms (Windows, macOS, and Linux) enables developers to create beautiful, native-feeling applications with a single codebase. This guide will walk you through everything you need to know about building desktop applications with Flutter.
Getting Started with Desktop Development
1. Setup and Requirements
flutter config --enable-windows-desktop flutter config --enable-macos-desktop flutter config --enable-linux-desktop flutter create --platforms=windows,macos,linux my_desktop_app
2. Platform-Specific Configuration
Windows
<!-- windows/runner/main.cpp --> #include <flutter/dart_project.h> #include <flutter/flutter_window.h> #include <flutter/standard_method_codec.h> #include <memory> int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t *command_line, int show_command) { flutter::DartProject project(L"data"); std::vector<std::string> command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); flutter::FlutterWindow window(project); window.SetTitle(L"My Desktop App"); window.SetIcon("assets/icon.ico"); window.Run(); return 0; }
macOS
// macos/Runner/AppDelegate.swift import Cocoa import FlutterMacOS class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } }
Linux
// linux/my_application.cc #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); }
Desktop-Specific Features
1. Window Management
import 'package:window_manager/window_manager.dart'; class MainWindow extends StatefulWidget { @override _MainWindowState createState() => _MainWindowState(); } class _MainWindowState extends State<MainWindow> with WindowListener { @override void initState() { super.initState(); windowManager.addListener(this); _init(); } Future<void> _init() async { await windowManager.setMinimumSize(const Size(800, 600)); await windowManager.setTitle('My Desktop App'); await windowManager.center(); } @override void onWindowClose() async { bool isPreventClose = await windowManager.isPreventClose(); if (isPreventClose) { showDialog( context: context, builder: (_) { return AlertDialog( title: Text('Are you sure?'), content: Text('Unsaved changes will be lost.'), actions: [ TextButton( child: Text('No'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: Text('Yes'), onPressed: () { Navigator.of(context).pop(); windowManager.destroy(); }, ), ], ); }, ); } } }
2. Menu Bar Integration
import 'package:menubar/menubar.dart'; class MenuBarExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Menu Bar Example'), ), body: Center( child: ElevatedButton( onPressed: () { setApplicationMenu([ Submenu(label: 'File', children: [ MenuItem( label: 'New', onClicked: () => print('New clicked'), ), MenuItem( label: 'Open', onClicked: () => print('Open clicked'), ), MenuDivider(), MenuItem( label: 'Exit', onClicked: () => windowManager.close(), ), ]), Submenu(label: 'Edit', children: [ MenuItem( label: 'Cut', onClicked: () => print('Cut clicked'), ), MenuItem( label: 'Copy', onClicked: () => print('Copy clicked'), ), MenuItem( label: 'Paste', onClicked: () => print('Paste clicked'), ), ]), ]); }, child: Text('Show Menu Bar'), ), ), ); } }
3. System Tray Integration
import 'package:tray_manager/tray_manager.dart'; class SystemTrayExample extends StatefulWidget { @override _SystemTrayExampleState createState() => _SystemTrayExampleState(); } class _SystemTrayExampleState extends State<SystemTrayExample> with TrayListener { @override void initState() { super.initState(); trayManager.addListener(this); _init(); } Future<void> _init() async { await trayManager.setIcon( 'assets/icon.png', ); await trayManager.setToolTip('My Desktop App'); await trayManager.setContextMenu([ MenuItem( label: 'Show', onClicked: () => windowManager.show(), ), MenuItem( label: 'Hide', onClicked: () => windowManager.hide(), ), MenuDivider(), MenuItem( label: 'Exit', onClicked: () => windowManager.destroy(), ), ]); } @override void onTrayIconMouseDown() { windowManager.show(); } }
Platform-Specific Considerations
1. File System Access
import 'package:file_selector/file_selector.dart'; class FileSystemExample extends StatelessWidget { Future<void> _openFile() async { final typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], ); final file = await openFile( acceptedTypeGroups: [typeGroup], ); if (file != null) { print('Selected file: ${file.name}'); } } Future<void> _saveFile() async { final path = await getSavePath(); if (path != null) { final file = File(path); await file.writeAsString('Hello, World!'); } } }
2. Native Integration
import 'package:window_size/window_size.dart'; class NativeIntegrationExample extends StatelessWidget { Future<void> _getScreenInfo() async { final screen = await getCurrentScreen(); print('Screen size: ${screen?.frame.width}x${screen?.frame.height}'); } Future<void> _setWindowPosition() async { await setWindowFrame(Rect.fromLTWH(100, 100, 800, 600)); } }
Best Practices
1. Responsive Design
class ResponsiveLayout extends StatelessWidget { @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth > 1200) { return DesktopLayout(); } else if (constraints.maxWidth > 800) { return TabletLayout(); } else { return MobileLayout(); } }, ); } }
2. Performance Optimization
class PerformanceOptimizedWidget extends StatelessWidget { @override Widget build(BuildContext context) { return RepaintBoundary( child: const Text('Optimized Widget'), ); } }
Common Issues and Solutions
1. Window Management
class WindowManagementExample extends StatefulWidget { @override _WindowManagementExampleState createState() => _WindowManagementExampleState(); } class _WindowManagementExampleState extends State<WindowManagementExample> { Future<void> _handleWindowState() async { // Check if window is minimized final isMinimized = await windowManager.isMinimized(); if (isMinimized) { await windowManager.restore(); } // Check if window is maximized final isMaximized = await windowManager.isMaximized(); if (isMaximized) { await windowManager.unmaximize(); } } }
2. Platform-Specific Features
class PlatformFeaturesExample extends StatelessWidget { Future<void> _handlePlatformFeatures() async { if (Platform.isWindows) { // Windows-specific features await windowManager.setTitleBarStyle(TitleBarStyle.hidden); } else if (Platform.isMacOS) { // macOS-specific features await windowManager.setTitleBarStyle(TitleBarStyle.hidden); } else if (Platform.isLinux) { // Linux-specific features await windowManager.setTitleBarStyle(TitleBarStyle.normal); } } }
Conclusion
Building desktop applications with Flutter involves:
- Setting up the development environment
- Implementing platform-specific features
- Managing windows and system integration
- Optimizing performance
- Handling platform-specific considerations
Remember to:
- Test on all target platforms
- Implement proper window management
- Consider platform-specific UI/UX
- Optimize for performance
- Handle platform-specific features appropriately
With these techniques, you can create powerful and native-feeling desktop applications using Flutter!