WindowCloseGuard

A Flutter package for safe window close handling on desktop with confirmation dialog support.

Features

  • ✅ Intercepts the window close event on Windows, macOS and Linux
  • ✅ Shows a customizable confirmation dialog before closing
  • ✅ Executes async cleanup tasks (saving data, logs, etc.) before the window is destroyed
  • ✅ Handles the close lifecycle correctly on all platforms
  • ✅ Supports dynamic dialog text via dialogConfigBuilder (useful for localization)

Platform support

Platform Supported
Windows
macOS
Linux
Android
iOS
Web

Installation

Add window_close_guard to your pubspec.yaml:

dependencies:
  window_close_guard: ^1.1.0

Then run:

flutter pub get

Usage

Call WindowCloseGuard.initialize() in didChangeDependencies() of your first stateful widget:

class _MyHomePageState extends State<MyHomePage> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    WindowCloseGuard.initialize(
      context: context,
      onClose: () async {
        await MyPreferences.save();
        await MyLogger.saveLogs();
      },
      dialogConfig: CloseDialogConfig(
        title: 'Exit',
        content: 'Are you sure you want to exit?',
        confirmText: 'Yes',
        cancelText: 'No',
      ),
    );
  }
}

With localization (dynamic dialog text)

Use dialogConfigBuilder instead of dialogConfig when your dialog text depends on the current locale or any other runtime value. The builder is called at the moment the dialog is shown, so it always reflects the current state:

WindowCloseGuard.initialize(
  context: context,
  onClose: () async {
    await MyPreferences.save();
  },
  dialogConfigBuilder: () => CloseDialogConfig(
    title: AppLocalizations.of(context)!.exit_title,
    content: AppLocalizations.of(context)!.exit_body,
    confirmText: AppLocalizations.of(context)!.yes,
  ),
);

Without a confirmation dialog

If you just want to run cleanup tasks without asking the user for confirmation:

WindowCloseGuard.initialize(
  context: context,
  showDialog: false,
  onClose: () async {
    await MyPreferences.save();
  },
);

Default behavior

Calling initialize() without optional parameters shows a generic confirmation dialog:

WindowCloseGuard.initialize(context: context);

API Reference

WindowCloseGuard.initialize()

Parameter Type Required Description
context BuildContext Yes The app's build context
onClose Future<void> Function()? No Async callback executed before the window closes
dialogConfig CloseDialogConfig? No Static configuration for the confirmation dialog. If null, a default dialog is shown
dialogConfigBuilder CloseDialogConfig Function()? No Dynamic configuration builder called at close time. Takes priority over dialogConfig. Useful for localization
showDialog bool No Whether to show a confirmation dialog before closing. Defaults to true

Note: dialogConfigBuilder takes priority over dialogConfig. If both are provided, dialogConfigBuilder is used.

CloseDialogConfig

Parameter Type Required Description
title String No Dialog title. Defaults to "Exit"
content String No Dialog body text. Defaults to "Are you sure you want to exit?"
confirmText String No Confirm button label. Defaults to "Yes"
cancelText String No Cancel button label. Defaults to "No"

Important notes

Why not windowManager.destroy()?

Internally, WindowCloseGuard uses setPreventClose(false) + close() instead of destroy() to close the window. This is intentional.

Calling destroy() directly bypasses the native close lifecycle of the operating system:

  • On Windows, Win32 expects the close flow to go through WM_CLOSEWM_DESTROY in order. Skipping this with destroy() leaves the Flutter process in an inconsistent state, causing the app to freeze and crash.
  • On macOS, Cocoa is more tolerant and cleans up automatically, so destroy() appears to work — but it's still not the correct approach.
  • On Linux, GTK has its own native close lifecycle similar to Win32, so the same issue is expected.

Using setPreventClose(false) + close() lets the OS handle the close flow natively and cleanly on all platforms.

Contributing

Contributions are welcome! Feel free to open an issue or submit a pull request on GitHub.

Please make sure to:

  • Follow the existing code style
  • Add tests for new functionality
  • Update the documentation if needed

License

This project is licensed under the MIT License. See the LICENSE file for details.

Libraries

window_close_guard