Flutter Alone
A Flutter plugin that ensures only a single instance of your desktop application runs at a time. When a duplicate is launched, it automatically focuses the original window and shows an alert message.
Platform Support
| Windows | macOS | Linux (X11) | Linux (Wayland) |
|---|---|---|---|
| ✅ | ✅ | ✅ | ⚠️ Partial |
- Windows: Detects duplicates using system-level Mutex with cross-user support
- macOS: Detects duplicates using advisory file locks (
flock) - Linux (X11): Full support — duplicate detection via
flockand window activation via_NET_ACTIVE_WINDOW - Linux (Wayland): Partial support — duplicate detection works reliably, but window activation is best-effort only. Wayland does not provide an API for cross-process window raising by design. The plugin falls back to
xdotoolvia XWayland; ifxdotoolis unavailable or the app is a native Wayland client, only the alert dialog is shown (no window activation).
Installation
dependencies:
flutter_alone: ^4.0.3
flutter pub get
Quick Start
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_alone/flutter_alone.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterAloneConfig? config;
if (Platform.isWindows) {
config = FlutterAloneConfig.forWindows(
windowsConfig: const DefaultWindowsMutexConfig(
packageId: 'com.example.myapp',
appName: 'My App',
),
messageConfig: const EnMessageConfig(),
);
} else if (Platform.isMacOS) {
config = FlutterAloneConfig.forMacOS(
macOSConfig: MacOSConfig(lockFileName: 'my_app.lock'),
messageConfig: const EnMessageConfig(),
);
} else if (Platform.isLinux) {
config = FlutterAloneConfig.forLinux(
linuxConfig: LinuxConfig(lockFileName: 'my_app.lock'),
messageConfig: const EnMessageConfig(),
);
}
if (config != null && !await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0); // Another instance is already running
}
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void dispose() {
FlutterAlone.instance.dispose(); // Always release resources
super.dispose();
}
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: Center(child: Text('Hello, World!'))),
);
}
}
API
FlutterAlone.instance
| Method | Return | Description |
|---|---|---|
checkAndRun(config:) |
Future<bool> |
Checks for a duplicate instance. Returns true if the app can start, false if another instance is already running. |
dispose() |
Future<void> |
Releases mutex/lock file resources. Must be called when the app exits. |
FlutterAloneConfig
Use platform-specific factory constructors to create the configuration.
// Windows
FlutterAloneConfig.forWindows(
windowsConfig: ..., // required - Mutex settings
messageConfig: ..., // required - Alert message settings
windowConfig: ..., // optional - Window identification
duplicateCheckConfig: ..., // optional - Debug mode behavior
)
// macOS
FlutterAloneConfig.forMacOS(
macOSConfig: ..., // required - Lock file settings
messageConfig: ..., // required - Alert message settings
windowConfig: ..., // optional - Window identification
duplicateCheckConfig: ..., // optional - Debug mode behavior
)
// Linux
FlutterAloneConfig.forLinux(
linuxConfig: ..., // required - Lock file settings
messageConfig: ..., // required - Alert message settings
windowConfig: ..., // optional - Window identification
duplicateCheckConfig: ..., // optional - Debug mode behavior
)
Configuration Options
Windows Mutex Config
Configures the system mutex name used for duplicate detection.
DefaultWindowsMutexConfig
Auto-generates a mutex name from your package ID and app name.
const DefaultWindowsMutexConfig(
packageId: 'com.example.myapp', // required
appName: 'My App', // required
mutexSuffix: 'prod', // optional
)
// Generated mutex name: "Global\com.example.myapp_My App_prod"
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
packageId |
String |
Yes | - | Package identifier (e.g., com.example.myapp) |
appName |
String |
Yes | - | Application name |
mutexSuffix |
String? |
No | null |
Suffix appended to the mutex name. Useful for distinguishing environments (e.g., dev/prod) |
CustomWindowsMutexConfig
Specify the mutex name directly.
CustomWindowsMutexConfig(
customMutexName: 'MyUniqueAppMutex', // required
)
// Automatically prefixed with "Global\" if not already present
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
customMutexName |
String |
Yes | - | Custom mutex name. Global\ prefix is added automatically if missing |
macOS Lock File Config
MacOSConfig
MacOSConfig(
lockFileName: 'my_app.lock', // optional
)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lockFileName |
String |
No | '.lockfile' |
Name of the lock file created in the system temp directory. Must be unique per app to avoid collisions |
Linux Lock File Config
LinuxConfig
LinuxConfig(
lockFileName: 'my_app.lock', // optional
)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lockFileName |
String |
No | '.lockfile' |
Name of the lock file created in /tmp. Must be unique per app to avoid collisions |
Note: On Wayland sessions, window activation of the existing instance requires
xdotool(run via XWayland). Native Wayland does not permit cross-process window raising, so on pure Wayland setups only the alert dialog will be shown when a duplicate is detected.
Message Config
Configures the alert message shown when a duplicate instance is detected.
EnMessageConfig - English (built-in)
const EnMessageConfig(
showMessageBox: true, // optional
)
KoMessageConfig - Korean (built-in)
const KoMessageConfig(
showMessageBox: true, // optional
)
CustomMessageConfig - Custom message
const CustomMessageConfig(
customTitle: 'Notice', // required
customMessage: 'The application is already running.', // required
showMessageBox: true, // optional
)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
customTitle |
String |
Yes | - | Title of the alert dialog |
customMessage |
String |
Yes | - | Body text of the alert dialog |
showMessageBox |
bool |
No | true |
Set to false to silently exit without showing a message box |
Window Config
WindowConfig
const WindowConfig(
windowTitle: 'My Application', // optional
)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
windowTitle |
String? |
No | null |
Window title used to locate the original window. Essential for system tray apps where the window may not be immediately visible. On Windows, the plugin enumerates all top-level windows matching this title and verifies the owning process path, so hidden/minimized windows are still reachable and windows from a different build that happens to share the same title are filtered out |
Duplicate Check Config
DuplicateCheckConfig
const DuplicateCheckConfig(
enableInDebugMode: true, // optional
)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enableInDebugMode |
bool |
No | false |
When true, performs the duplicate check even in debug mode. By default, the check is skipped in debug mode for a smoother development workflow |
Advanced Example
A full-featured example using all available options, ideal for system tray applications.
final config = FlutterAloneConfig.forWindows(
windowsConfig: CustomWindowsMutexConfig(
customMutexName: 'MyProductionAppMutex',
),
windowConfig: const WindowConfig(
windowTitle: 'My Application', // Needed to find the window when minimized to tray
),
duplicateCheckConfig: const DuplicateCheckConfig(
enableInDebugMode: true, // Test duplicate detection during development
),
messageConfig: const CustomMessageConfig(
customTitle: 'Application Notice',
customMessage: 'Another instance is already running. Check the system tray.',
showMessageBox: true,
),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
Platform-Specific Setup
Some platforms require additional native-side setup. See the Platform Setup Guide for details.
- Windows: Resolving
FindWindow()API conflicts when using thewindow_managerpackage - macOS: Handling app reactivation when the dock icon is clicked (critical for system tray apps)
FAQ
Q: Why do I need a different native window title when using window_manager?
A: The Windows FindWindow() API requires a unique native window title to locate the original window. The user only sees the final title set by window_manager.
Q: What if I don't use window_manager?
A: No additional platform setup is needed. The plugin works out of the box.
Q: The duplicate check doesn't work in debug mode.
A: By default, the check is skipped in debug mode. Set DuplicateCheckConfig(enableInDebugMode: true) to enable it.
Q: Two different apps using flutter_alone conflict with each other.
A: Both MacOSConfig and LinuxConfig default to .lockfile. Use a unique lockFileName per app (e.g., 'com.example.myapp.lock').
Q: I ship both an installed and a portable build of my app. How do I let them run side-by-side?
A: The two builds must be distinguishable at every identity layer the OS / this plugin inspects:
- Windows — give each build a different mutex name (e.g., derive it from the executable directory via
CustomWindowsMutexConfig). The plugin already verifies process path, so even if both builds share the samewindowTitle, the title fallback will skip windows from a different executable. - macOS — give each build a different
lockFileName, and also use a differentCFBundleIdentifierin the portable build'sInfo.plist. Launch Services treats apps with the same bundle ID as the same app, so the OS itself may coalesce them even before this plugin is reached. A post-buildPlistBuddystep is usually simpler than adding a new Xcode configuration. - Linux — give each build a different
lockFileName. Process identity is verified via/proc/<pid>/exe, so activation of an existing instance will never cross over to a different executable.
Contributing
Contributions are welcome! Please submit a pull request or create an issue.