A robust Flutter plugin for preventing duplicate execution of desktop applications, offering advanced process management, window control, and cross-user detection.
-
Duplicate Execution Prevention
- System-wide mutex management
- Cross-user account detection
- Process-level duplicate checking
- Debug mode support with configurable options
- Customizable mutex naming strategies:
- Application ID based naming (DefaultMutexConfig)
- Custom mutex name specification (CustomMutexConfig)
-
Window Management
- Automatic window focusing
- Window restoration handling
- Bring to front functionality
- Enhanced taskbar identification
- Rich MessageBox with application icon
- Window detection by window title
- System tray application support
-
Customizable Messaging
- Multi-language support (English/Korean)
- Custom message templates
- Configurable message box display
- UTF-8 text encoding support
-
Process Management
- Detailed process information tracking
- Safe resource cleanup
- Robust error handling
Windows | macOS | Linux |
---|---|---|
âś… | đźš§ | đźš§ |
If you're using flutter_alone with the window_manager package, you MUST configure window titles correctly to avoid detection failures.
When window_manager and flutter_alone use identical window titles, window detection fails in system tray scenarios.
// main.dart
const String appTitle = 'My Flutter App';
WindowOptions windowOptions = const WindowOptions(
title: appTitle, // "My Flutter App"
);
FlutterAloneConfig config = FlutterAloneConfig(
windowConfig: const WindowConfig(
windowTitle: appTitle, // "My Flutter App" (same!)
),
messageConfig: const EnMessageConfig(),
);
// windows/runner/main.cpp
if (!window.Create(L"My Flutter App", origin, size)) { // Same title!
return EXIT_FAILURE;
}
// main.dart - Keep your desired titles
const String appTitle = 'My Flutter App';
WindowOptions windowOptions = const WindowOptions(
title: appTitle, // "My Flutter App" (user-visible)
);
FlutterAloneConfig config = FlutterAloneConfig(
windowConfig: const WindowConfig(
windowTitle: appTitle, // "My Flutter App" (detection)
),
messageConfig: const EnMessageConfig(),
);
// windows/runner/main.cpp - Make this different!
if (!window.Create(L"MyFlutterApp", origin, size)) { // 🎯 Different!
return EXIT_FAILURE;
}
// BEFORE (same title - causes issues)
if (!window.Create(L"My Flutter App", origin, size)) {
return EXIT_FAILURE;
}
// AFTER (remove spaces or use underscores)
if (!window.Create(L"MyFlutterApp", origin, size)) { // Remove spaces
return EXIT_FAILURE;
}
// OR
if (!window.Create(L"My_Flutter_App", origin, size)) { // Use underscores
return EXIT_FAILURE;
}
// Your flutter_alone and window_manager code stays the same
const String appTitle = 'My Flutter App'; // Spaces OK here!
WindowOptions windowOptions = const WindowOptions(
title: appTitle, // User sees this title
);
FlutterAloneConfig config = FlutterAloneConfig(
windowConfig: const WindowConfig(
windowTitle: appTitle, // flutter_alone uses this for detection
),
);
Flutter Windows apps create windows in two stages:
- Native Creation:
window.Create()
creates the initial Win32 window - Flutter Setup:
window_manager
later changes the title withSetWindowText()
When titles are identical, the Windows FindWindow()
API becomes unpredictable with multiple windows having the same name, causing detection failures.
This follows Microsoft's official recommendation to use unique window titles.
Display Title | Native Title (main.cpp) |
---|---|
"My App" |
"MyApp" |
"Flutter Chat" |
"FlutterChat" |
"Data Analyzer" |
"Data_Analyzer" |
"Music Player" |
"MusicPlayer" |
Add flutter_alone to your pubspec.yaml
:
dependencies:
flutter_alone: ^3.1.1
Import the package:
import 'package:flutter_alone/flutter_alone.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp'
),
messageConfig: const EnMessageConfig(),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
runApp(const MyApp());
}
When you need more direct control over mutex naming:
final config = FlutterAloneConfig(
mutexConfig: const CustomMutexConfig(
customMutexName: 'MyUniqueApplicationMutex',
),
messageConfig: const EnMessageConfig(),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp'
),
messageConfig: const KoMessageConfig(),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp'
),
messageConfig: const CustomMessageConfig(
customTitle: 'Application Notice',
customMessage: 'Application is already running',
showMessageBox: true, // Optional, defaults to true
),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
You can customize the default mutex name with package ID, app name and an optional suffix:
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp',
mutexSuffix: 'production',
),
messageConfig: const EnMessageConfig(),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
For system tray applications or when you need specific window identification:
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp',
),
windowConfig: const WindowConfig(
windowTitle: 'My Application Window Title', // Used for window detection
),
messageConfig: const CustomMessageConfig(
customTitle: 'Notice',
customMessage: 'Application is already running',
),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
The plugin provides special handling for debug mode:
- By default, duplicate checks are skipped in debug mode for better development experience
- You can enable duplicate checks in debug mode using the
enableInDebugMode
flag
// Default behavior (skips duplicate check in debug mode)
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp'
),
messageConfig: const EnMessageConfig(),
);
// Enable duplicate check even in debug mode
final config = FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp'
),
duplicateCheckConfig: const DuplicateCheckConfig(
enableInDebugMode: true
),
messageConfig: const EnMessageConfig(),
);
Here are examples with all configuration options:
Using DefaultMutexConfig:
final config = FlutterAloneConfig(
// Default Mutex configuration
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp',
mutexSuffix: 'production',
),
// Window configuration
windowConfig: const WindowConfig(
windowTitle: 'My Application Window',
),
// Duplicate check configuration
duplicateCheckConfig: const DuplicateCheckConfig(
enableInDebugMode: true,
),
// Message configuration
messageConfig: const CustomMessageConfig(
customTitle: 'Application Notice',
customMessage: 'Application is already running',
showMessageBox: true,
),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
Using CustomMutexConfig:
final config = FlutterAloneConfig(
// Custom Mutex configuration
mutexConfig: const CustomMutexConfig(
customMutexName: 'MyUniqueAppMutex',
),
// Window configuration
windowConfig: const WindowConfig(
windowTitle: 'My Application Window',
),
// Duplicate check configuration
duplicateCheckConfig: const DuplicateCheckConfig(
enableInDebugMode: true,
),
// Message configuration
messageConfig: const CustomMessageConfig(
customTitle: 'Application Notice',
customMessage: 'Application is already running',
showMessageBox: true,
),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
For system tray applications, you can use the windowTitle parameter to help the plugin locate and activate your existing window:
final config = FlutterAloneConfig(
mutexConfig: const CustomMutexConfig(
customMutexName: 'MySystemTrayApp',
),
windowConfig: const WindowConfig(
windowTitle: 'My System Tray App',
),
messageConfig: const CustomMessageConfig(
customTitle: 'Notice',
customMessage: 'Application is already running',
),
);
if (!await FlutterAlone.instance.checkAndRun(config: config)) {
exit(0);
}
See the example project for a complete system tray implementation with flutter_alone.
Always remember to dispose of resources when your application closes:
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void dispose() {
FlutterAlone.instance.dispose();
super.dispose();
}
// ... rest of your widget implementation
}
The plugin now provides two ways to configure mutex names:
- DefaultMutexConfig: Uses package ID and app name to generate mutex names (traditional approach)
- CustomMutexConfig: Allows direct specification of mutex name (new approach)
The plugin provides a modular configuration system:
FlutterAloneConfig
: Main configuration containerMutexConfig
: Abstract base class for mutex configurationDefaultMutexConfig
: Controls mutex naming using package ID and app nameCustomMutexConfig
: Controls mutex naming with a custom name
WindowConfig
: Window detection settingsDuplicateCheckConfig
: Controls debug mode behaviorMessageConfig
: Message display settings (includesEnMessageConfig
,KoMessageConfig
, andCustomMessageConfig
)
- Uses Windows Named Mutex for system-wide instance detection
- Implements robust cross-user detection through global mutex naming
- Ensures proper cleanup of system resources
- Full Unicode support for international character sets
- Advanced window management capabilities
- Enhanced taskbar and message box icon handling
- Customizable mutex naming with sanitization and validation
- Window detection and activation for system tray applications
The plugin provides detailed error information through the AloneException
class:
try {
await FlutterAlone.instance.checkAndRun(
config: FlutterAloneConfig(
mutexConfig: const DefaultMutexConfig(
packageId: 'com.example.myapp',
appName: 'MyFlutterApp'
),
messageConfig: const EnMessageConfig(),
)
);
} on AloneException catch (e) {
print('Error Code: ${e.code}');
print('Message: ${e.message}');
print('Details: ${e.details}');
}
-
Use DefaultMutexConfig when:
- You want automatic mutex name generation based on your app's identity
- You need backward compatibility with earlier versions
- You want the plugin to handle name generation and sanitization
-
Use CustomMutexConfig when:
- You need full control over mutex naming
- You have specific mutex naming requirements
- You want to use the same mutex across different applications
A: Flutter apps create windows in two stages. Having different native titles prevents Windows' FindWindow API from returning unpredictable results when multiple windows temporarily have the same title.
A: No! The user only sees the title set by window_manager. The native title is used internally for window detection.
A: Yes! You can create a helper function to automatically generate native titles by removing spaces and special characters.
A: If you're not using window_manager, you don't need to worry about this issue. The plugin will work normally.
A: This is a Windows-specific issue. When macOS and Linux support are added, they may have different requirements.
If your system tray application isn't being detected properly:
- Check window titles: Ensure native and display titles follow the guidelines above
- Verify window_manager setup: Make sure window_manager is properly initialized
- Enable debug mode: Set
enableInDebugMode: true
to test detection in development - Check example project: Reference the included system tray example
If existing windows aren't being brought to front:
- Verify window title configuration: Follow the window_manager setup guide
- Check window state: Ensure the window isn't in an invalid state
- Test focus behavior: Some security software may prevent window activation
Contributions are welcome! Please read our contributing guidelines before submitting pull requests.