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.

pub package

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 flock and 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 xdotool via XWayland; if xdotool is 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 the window_manager package
  • 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 same windowTitle, the title fallback will skip windows from a different executable.
  • macOS — give each build a different lockFileName, and also use a different CFBundleIdentifier in the portable build's Info.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-build PlistBuddy step 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.