MultiWindowManager

pub version

Flutter desktop plugin for creating and managing multiple windows: resizing, repositioning, and inter-window communication.

Fork and re-work of window_manager_plus, which itself is based on window_manager. Key additions: window reuse cache (avoid re-creating Flutter engines), cross-window registry backed by shared native state, fixed critical errors fixed restart and crash after close secondary window, fast isolate-isolate channel fast channel for realtime windows notify - ipc.

Linux is not currently supported.



Platform Support

Linux macOS Windows
n/a + +

Setup

Windows setup

Edit windows/runner/main.cpp:

 #include <flutter/dart_project.h>
 #include <flutter/flutter_view_controller.h>
 #include <windows.h>

+#include <iostream>
 #include "flutter_window.h"
 #include "utils.h"
+#include "multi_window_manager/multi_window_manager_plugin.h"

 int APIENTRY wWinMain(...) {
   ...

   FlutterWindow window(project);
   Win32Window::Point origin(10, 10);
   Win32Window::Size size(1280, 720);
   if (!window.CreateAndShow(L"my_app", origin, size)) {
     return EXIT_FAILURE;
   }
-  window.SetQuitOnClose(true);
+  window.SetQuitOnClose(false);  // let MultiWindowManager decide when to quit

+  MultiWindowManagerPluginSetWindowCreatedCallback(
+      [](std::vector<std::string> command_line_arguments) {
+        flutter::DartProject project(L"data");
+        project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+        auto window = std::make_shared<FlutterWindow>(project);
+        Win32Window::Point origin(10, 10);
+        Win32Window::Size size(1280, 720);
+        if (!window->CreateAndShow(L"my_app", origin, size)) {
+          std::cerr << "Failed to create window" << std::endl;
+        }
+        window->SetQuitOnClose(false);
+        return std::move(window);
+      });

   ::MSG msg;
   ...

SetQuitOnClose(false) prevents each child window from terminating the process when it closes.

macOS setup

Edit macos/Runner/MainFlutterWindow.swift:

 import Cocoa
 import FlutterMacOS
+import multi_window_manager

 class MainFlutterWindow: NSPanel {
     override func awakeFromNib() {
         ...
         RegisterGeneratedPlugins(registry: flutterViewController)
+        MultiWindowManagerPlugin.RegisterGeneratedPlugins = RegisterGeneratedPlugins
         super.awakeFromNib()
     }

+    override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
+        super.order(place, relativeTo: otherWin)
+        hiddenWindowAtLaunch()
+    }
 }

Edit macos/Runner/AppDelegate.swift:

 import Cocoa
 import FlutterMacOS
+import multi_window_manager

 @main
 class AppDelegate: FlutterAppDelegate {
   override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
-    return true
+    // Close the app only when windows not contains MainFlutterWindow
+    return !NSApp.windows.contains(where: { $0 is MainFlutterWindow })
   }
 }

Usage

Initialization

Call ensureInitialized once before accessing MultiWindowManager.current.

The args list passed to main() carries the window ID in args[0] (absent for the main window).

import 'package:flutter/material.dart';
import 'package:multi_window_manager/multi_window_manager.dart';

void main(List<String> args) async {
  WidgetsFlutterBinding.ensureInitialized();

  final windowId = args.isEmpty ? 0 : int.tryParse(args[0]) ?? 0;

  if (windowId == 0) {
    // Main window - never participates in the reuse cache.
    await MultiWindowManager.ensureInitialized(windowId);
  } else {
    // Secondary window - optionally enable reuse cache.
    await MultiWindowManager.ensureInitializedSecondary(
      windowId,
      isEnabledReuse: true, // hide instead of destroy on close
    );
  }

  WindowOptions windowOptions = const WindowOptions(
    size: Size(800, 600),
    center: true,
    backgroundColor: Colors.transparent,
    titleBarStyle: TitleBarStyle.hidden,
  );

  MultiWindowManager.current.waitUntilReadyToShow(windowOptions, () async {
    await MultiWindowManager.current.show();
    await MultiWindowManager.current.focus();
  });

  runApp(const MyApp());
}

Create a window

// Spawn a new Flutter engine window, passing optional string arguments.
final newWindow = await MultiWindowManager.createWindow(['type=dashboard', 'userId=42']);
if (newWindow != null) {
  print('Created window ${newWindow.id}');
}

The arguments are available in main(List<String> args) of the new window as args[1], args[2], etc.

Reuse cached windows

createWindowOrReuse avoids spawning a new Flutter engine when a previously-hidden reuse-enabled window is available. Claiming a hidden window is atomic - concurrent calls from different windows cannot pick the same target.

// Any window can call this - main or secondary.
final window = await MultiWindowManager.createWindowOrReuse(
  args: ['type=dashboard', 'userId=42'],
);

For a window to participate in the reuse cache:

  1. Initialize it with isEnabledReuse: true (see Initialization).
  2. Use ReuseWindow as the root widget:
// Secondary window entry-point:
runApp(MaterialApp(
  home: ReuseWindow(
    initialArgs: parsedArgs,
    windowOptions: const WindowOptions(
      size: Size(1280, 720),
      center: true,
      titleBarStyle: TitleBarStyle.hidden,
    ),
    loadingBuilder: (context) => const Center(child: CircularProgressIndicator()),
    builder: (context, args) {
      // Called on first show AND on every reuse with the latest args.
      return MyPage(args: args);
    },
  ),
));

When the user closes a ReuseWindow, the native layer hides the OS window instead of destroying the Flutter engine. createWindowOrReuse can later reclaim it with new args, rebuilding the content without the overhead of creating a new engine.

setPreventClose works independently from the reuse mechanism, so inner widgets can still show "are you sure?" dialogs without breaking reuse.

Communication between windows

// Send an event from any window to window with id 1.
final result = await MultiWindowManager.current.invokeMethodToWindow(
  1,
  'showNotification',
  {'message': 'Hello from window ${MultiWindowManager.current.id}'},
);

// In window 1 - implement WindowListener.onEventFromWindow:
class _MyWidgetState extends State<MyWidget> with WindowListener {
  @override
  void initState() {
    super.initState();
    MultiWindowManager.current.addListener(this);
  }

  @override
  void dispose() {
    MultiWindowManager.current.removeListener(this);
    super.dispose();
  }

  @override
  Future<dynamic> onEventFromWindow(
      String eventName, int fromWindowId, dynamic arguments) async {
    if (eventName == 'showNotification') {
      print('Message from $fromWindowId: ${arguments['message']}');
      return 'acknowledged';
    }
    return null;
  }
}

To get all registered window IDs:

final ids = await MultiWindowManager.getAllWindowManagerIds();

High-frequency IPC (WindowIpc)

WindowIpc is a persistent channel built on Dart's IsolateNameServer. After a one-time native handshake, all data flows through SendPort / ReceivePort with no native layer involvement. This makes it suitable for animation-rate state sync, ChangeNotifier updates, and any scenario where invokeMethodToWindow would be called too frequently.

The channel is a lazy singleton on each window - no setup or disposal required:

final ipc = MultiWindowManager.current.ipc;

Wire format: a flat List<Object?>. List index access has no hashing overhead compared to Map, which matters at high message rates.

Connect

One call establishes a bidirectional link. Both windows can send and receive after a single handshake:

// Window A: connect to window B.
await MultiWindowManager.current.ipc.connectWindow(windowBId);

// Window B: react when A connects.
MultiWindowManager.current.ipc.onConnected = (int fromId) {
  // Set up stream listeners here.
};

// Either side: react when the peer closes or hides.
MultiWindowManager.current.ipc.onDisconnected = (int fromId) {
  // Clean up listeners here.
};

Send and receive raw payloads

// Sender: fire-and-forget, no await needed.
await MultiWindowManager.current.ipc.notifyWindow(targetId, ['counter', 1, 'ts', timestamp]);

// Receiver:
MultiWindowManager.current.ipc.listenWindow(sourceId).listen((payload) {
  final counter = payload[1] as int;
});

Use a topic string at index 0 to route messages between multiple features sharing the same channel:

ipc.notifyWindow(targetId, ['metrics', 'fps', 60, 'mem', 128]);
ipc.notifyWindow(targetId, ['input',   'x',   0.5, 'y',  0.3]);

ipc.listenWindow(sourceId)
    .where((m) => m[0] == 'metrics')
    .listen((m) => /* ... */);

Sync a ChangeNotifier

Use IpcNotifierSender on the source window and IpcNotifierReceiver on the target window.

Multiple fields changed in the same synchronous call stack are coalesced into one message via scheduleMicrotask, so rapid successive assignments produce a single IPC event instead of one per field.

Source window:

class PersonNotifier extends ChangeNotifier with IpcNotifierSender {
  String _name = '';
  int _age = 0;

  String get name => _name;
  set name(String v) {
    _name = v;
    notifyListeners();
    ipcMark('name', v); // queues field for next microtask flush
  }

  int get age => _age;
  set age(int v) {
    _age = v;
    notifyListeners();
    ipcMark('age', v);
  }
}

// Setup (once, after ensureInitialized):
final notifier = PersonNotifier();
notifier.ipcSetup(MultiWindowManager.current.ipc, 'person');

// Both fields changed synchronously = ONE IPC message.
notifier.name = 'Alice';
notifier.age  = 30;

Target window:

class PersonNotifier extends ChangeNotifier with IpcNotifierReceiver {
  String name = '';
  int age = 0;

  @override
  void ipcApplyField(String field, Object? value) {
    switch (field) {
      case 'name': name = value as String;
      case 'age':  age  = value as int;
    }
    // notifyListeners() is called once after the full batch.
  }

  @override
  void dispose() {
    ipcCancelListeners();
    super.dispose();
  }
}

// Setup: call inside ipc.onConnected so the receiver starts when the
// source window connects (not before).
MultiWindowManager.current.ipc.onConnected = (int fromId) {
  myNotifier.ipcListen(MultiWindowManager.current.ipc, fromId, 'person');
};

Multiple notifiers can share the same channel by using different topic strings:

personNotifier.ipcSetup(ipc, 'person');
productNotifier.ipcSetup(ipc, 'product');

personNotifier.ipcListen(ipc, sourceId, 'person');
productNotifier.ipcListen(ipc, sourceId, 'product');

Performance comparison

Approach 1 000 messages sent Receive throughput Notes
WindowIpc (fire-and-forget) ~15 ms ~66 000 msg/s No native layer after handshake
invokeMethodToWindow (no await) ~15 ms ~20 000 msg/s Platform thread is bottleneck
invokeMethodToWindow (await each) ~1 600 ms ~625 msg/s Full round-trip per call

Window events

class _MyWidgetState extends State<MyWidget> with WindowListener {
  @override
  void initState() {
    super.initState();
    MultiWindowManager.current.addListener(this);
  }

  @override
  void dispose() {
    MultiWindowManager.current.removeListener(this);
    super.dispose();
  }

  @override
  void onWindowFocus([int? windowId]) => setState(() {});

  @override
  void onWindowClose([int? windowId]) { /* ... */ }

  @override
  void onWindowMaximize([int? windowId]) { /* ... */ }

  @override
  void onWindowMinimize([int? windowId]) { /* ... */ }

  // Catches every event by name, including reuse events.
  @override
  void onWindowEvent(String eventName, [int? windowId]) {
    print('event: $eventName  window: $windowId');
  }
}

For global listeners (receive events from all windows with windowId set):

MultiWindowManager.addGlobalListener(myGlobalListener);
MultiWindowManager.removeGlobalListener(myGlobalListener);

Window registry

MultiWindowManager.registry is a per-isolate reactive view backed by the shared native state. Use it to drive UI that reflects the live window list.

// In a widget:
ValueListenableBuilder<List<int>>(
  valueListenable: MultiWindowManager.registry.activeWindows,
  builder: (context, ids, _) => Text('Open windows: $ids'),
);

ValueListenableBuilder<List<int>>(
  valueListenable: MultiWindowManager.registry.hiddenWindows,
  builder: (context, ids, _) => Text('Cached (reusable) windows: $ids'),
);

For a guaranteed-fresh read bypassing the local cache:

final activeIds = await MultiWindowManager.registry.getActiveWindowIds();
final hiddenIds = await MultiWindowManager.registry.getHiddenWindowIds();

Confirm before closing

class _MyWidgetState extends State<MyWidget> with WindowListener {
  @override
  void initState() {
    super.initState();
    MultiWindowManager.current.addListener(this);
    MultiWindowManager.current.setPreventClose(true);
  }

  @override
  void dispose() {
    MultiWindowManager.current.removeListener(this);
    super.dispose();
  }

  @override
  void onWindowClose([int? windowId]) async {
    if(!await MultiWindowManager.current.isPreventClose()) return;
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (_) => AlertDialog(
        title: const Text('Close window?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: const Text('Close'),
          ),
        ],
      ),
    );
    if (confirmed == true) {
      await MultiWindowManager.current.setPreventClose(false);
      await MultiWindowManager.current.close();
    }
  }
}

Quit on close

macOS

 override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
-  return true
+  return false
 }

Windows

Set SetQuitOnClose(false) for every window (see Windows setup). To exit when the main window closes, handle onWindowClose and call exit(0).

Hidden at launch

Windows

Edit windows/runner/win32_window.cpp to create the window without WS_VISIBLE:

 HWND window = CreateWindow(
-    window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+    window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
     ...);

Edit windows/runner/flutter_window.cpp to remove the auto-show:

 flutter_controller_->engine()->SetNextFrameCallback([&]() {
-  this->Show();
 });

API

MultiWindowManager

Instance methods

addListener(WindowListener listener) -> void

Subscribe to window events for this window.

removeListener(WindowListener listener) -> void

Unsubscribe from window events.

show({bool inactive = false}) -> Future<void>

Shows and gives focus to the window.

hide() -> Future<void>

Hides the window.

close() -> Future<void>

Try to close the window (respects setPreventClose).

destroy() -> Future<void>

Force-close app, bypassing setPreventClose.

focus() -> Future<void>

Focuses on the window.

blur() -> Future<void>

Removes focus from the window.

isFocused() -> Future<bool>

Returns whether the window is currently focused.

center({bool animate = false}) -> Future<void>

Moves the window to the center of the screen.

setPosition(Offset position, {bool animate = false}) -> Future<void>

Moves the window to a position.

getPosition() -> Future<Offset>

Returns the current window position.

setAlignment(Alignment alignment, {bool animate = false}) -> Future<void>

Moves the window to a screen-aligned position.

setSize(Size size, {bool animate = false}) -> Future<void>

Resizes the window.

getSize() -> Future<Size>

Returns the current window size.

setBounds(Rect? bounds, {Offset? position, Size? size, bool animate = false}) -> Future<void>

Resizes and moves the window to the supplied bounds.

getBounds() -> Future<Rect>

Returns the window bounds as a Rect.

setMinimumSize(Size size) -> Future<void>

Sets the minimum window size.

setMaximumSize(Size size) -> Future<void>

Sets the maximum window size.

maximize({bool vertically = false}) -> Future<void>

Maximizes the window. vertically simulates aero snap (Windows only).

unmaximize() -> Future<void>

Unmaximizes the window.

isMaximized() -> Future<bool>

Returns whether the window is maximized.

isMaximizable() -> Future<bool>

Returns whether the window can be maximized by the user.

setMaximizable(bool isMaximizable) -> Future<void>

Sets whether the window can be maximized by the user.

minimize() -> Future<void>

Minimizes the window.

restore() -> Future<void>

Restores the window from a minimized state.

isMinimized() -> Future<bool>

Returns whether the window is minimized.

isMinimizable() -> Future<bool>

Returns whether the window can be minimized by the user.

setMinimizable(bool isMinimizable) -> Future<void>

Sets whether the window can be minimized by the user.

setFullScreen(bool isFullScreen) -> Future<void>

Sets whether the window should be in full-screen mode.

isFullScreen() -> Future<bool>

Returns whether the window is in full-screen mode.

isVisible() -> Future<bool>

Returns whether the window is visible to the user.

setTitle(String title) -> Future<void>

Changes the title of the native window.

getTitle() -> Future<String>

Returns the title of the native window.

setTitleBarStyle(TitleBarStyle titleBarStyle, {bool windowButtonVisibility = true}) -> Future<void>

Changes the title bar style of the native window.

getTitleBarHeight() -> Future<int>

Returns the title bar height in logical pixels.

setAlwaysOnTop(bool isAlwaysOnTop) -> Future<void>

Sets whether the window is always on top of other windows.

isAlwaysOnTop() -> Future<bool>

Returns whether the window is always on top.

setAlwaysOnBottom(bool isAlwaysOnBottom) -> Future<void>

Sets whether the window is always below other windows.

isAlwaysOnBottom() -> Future<bool>

Returns whether the window is always on the bottom.

setOpacity(double opacity) -> Future<void>

Sets the window opacity (0.0 fully transparent, 1.0 fully opaque).

getOpacity() -> Future<double>

Returns the current window opacity.

setBackgroundColor(Color backgroundColor) -> Future<void>

Sets the background color of the window.

setPreventClose(bool isPreventClose) -> Future<void>

Intercepts the native close signal. Use with onWindowClose to show a confirmation dialog.

isPreventClose() -> Future<bool>

Returns whether the native close signal is being intercepted.

setResizable(bool isResizable) -> Future<void>

Sets whether the window can be resized by the user.

isResizable() -> Future<bool>

Returns whether the window is resizable.

setMovable(bool isMovable) -> Future<void>

Sets whether the window can be moved by the user.

isMovable() -> Future<bool>

Returns whether the window is movable.

setClosable(bool isClosable) -> Future<void>

Sets whether the window can be manually closed by the user.

isClosable() -> Future<bool>

Returns whether the window can be closed by the user.

setSkipTaskbar(bool isSkipTaskbar) -> Future<void>

Makes the window not show in the taskbar / dock.

isSkipTaskbar() -> Future<bool>

Returns whether the window is hidden from the taskbar.

setHasShadow(bool hasShadow) -> Future<void>

Sets whether the window has a shadow (frameless windows only on Windows).

hasShadow() -> Future<bool>

Returns whether the window has a shadow.

setProgressBar(double progress) -> Future<void>

Sets the taskbar progress bar value (0.0 to 1.0). Windows only.

setIcon(String iconPath) -> Future<void>

Sets the window / taskbar icon.

dock({required DockSide side, required int width}) -> Future<void>

Docks the window to a screen edge. Windows only.

undock() -> Future<bool>

Undocks the window. Windows only.

isDocked() -> Future<DockSide?>

Returns the current dock side, or null if not docked.

setAsFrameless() -> Future<void>

Removes the native window frame (title bar, border, etc.).

setAspectRatio(double aspectRatio) -> Future<void>

Locks the window to a fixed aspect ratio.

setBrightness(Brightness brightness) -> Future<void>

Sets the brightness (light/dark) of the window.

setIgnoreMouseEvents(bool ignore, {bool forward = false}) -> Future<void>

Makes the window ignore all mouse events.

popUpWindowMenu() -> Future<void>

Pops up the native window menu.

startDragging() -> Future<void>

Starts a window drag from a custom title bar widget.

startResizing(ResizeEdge resizeEdge) -> Future<void>

Starts a window resize from a custom border widget.

waitUntilReadyToShow(WindowOptions? options, VoidCallback? callback) -> Future<void>

Applies WindowOptions and calls callback once the window is ready to display.

invokeMethodToWindow(int targetWindowId, String method, dynamic args) -> Future

Sends an event to another window. The result is the return value of WindowListener.onEventFromWindow in the target window.

getDevicePixelRatio() -> double

Returns the device pixel ratio for this window.

toString() -> String

Returns a string representation including the window ID.

Static methods

ensureInitialized(int windowId) -> Future<void>

Initialize for the main window (windowId = 0). Must be called before accessing MultiWindowManager.current.

ensureInitializedSecondary(int windowId, {bool isEnabledReuse = false}) -> Future<void>

Initialize for a secondary window. Set isEnabledReuse: true to enable the reuse cache for this window.

createWindow(List\<String\>? args) -> Future<MultiWindowManager?>

Spawns a new window. Returns a MultiWindowManager instance for the new window, or null on failure.

createWindowOrReuse({List<String>? args}) -> Future<MultiWindowManager?>

Atomically claims a hidden reuse-cached window and reinitializes it with args, or spawns a new one if none are available. Safe to call from any window (main or secondary).

fromWindowId(int windowId) -> MultiWindowManager

Returns a MultiWindowManager instance for any registered window by ID.

getAllWindowManagerIds() -> Future<List<int>>

Returns the IDs of all windows currently registered in the process.

addGlobalListener(WindowListener listener) -> void

Subscribes to events from all windows. The windowId parameter in callbacks identifies the source window.

removeGlobalListener(WindowListener listener) -> void

Unsubscribes a global listener.

registry -> WindowRegistry

Process-wide reactive window registry. See WindowRegistry.


WindowListener

Mixin used with addListener / addGlobalListener. When used as a global listener, windowId identifies the source window; when used as a local listener it is always null.

onWindowClose(int? windowId) -> void

Emitted when the window is going to be closed.

onWindowFocus(int? windowId) -> void

Emitted when the window gains focus.

onWindowBlur(int? windowId) -> void

Emitted when the window loses focus.

onWindowMaximize(int? windowId) -> void

Emitted when the window is maximized.

onWindowUnmaximize(int? windowId) -> void

Emitted when the window exits a maximized state.

onWindowMinimize(int? windowId) -> void

Emitted when the window is minimized.

onWindowRestore(int? windowId) -> void

Emitted when the window is restored from a minimized state.

onWindowResize(int? windowId) -> void

Emitted while the window is being resized.

onWindowResized(int? windowId) -> void

Emitted once when the window finishes resizing. Windows / macOS only.

onWindowMove(int? windowId) -> void

Emitted while the window is being moved.

onWindowMoved(int? windowId) -> void

Emitted once when the window finishes moving. Windows / macOS only.

onWindowEnterFullScreen(int? windowId) -> void

Emitted when the window enters full-screen mode.

onWindowLeaveFullScreen(int? windowId) -> void

Emitted when the window leaves full-screen mode.

onWindowDocked(int? windowId) -> void

Emitted when the window enters a docked state. Windows only.

onWindowUndocked(int? windowId) -> void

Emitted when the window leaves a docked state. Windows only.

onWindowEvent(String eventName, int? windowId) -> void

Emitted for every window event, including reuse lifecycle events.

onEventFromWindow(String eventName, int fromWindowId, dynamic arguments) -> Future

Receives inter-window messages sent via invokeMethodToWindow. The return value is forwarded back to the caller.


WindowIpc

Accessible via MultiWindowManager.current.ipc. Lazy singleton - created on first access.

ipc -> WindowIpc<List<Object?>>

The IPC channel for this window. Payload type is always List<Object?>.

connectWindow(int targetId) -> Future<bool>

Establishes a bidirectional link with targetId. Returns false if the target has not yet accessed its ipc getter. After success, both windows can send and receive without further native calls.

notifyWindow(int targetId, List<Object?> data) -> Future<bool>

Sends data to targetId via SendPort. Calls connectWindow automatically on first use. Returns false if the target is not reachable.

listenWindow(int sourceId) -> Stream<List<Object?>>

Returns a broadcast stream of messages arriving from sourceId. Created lazily.

onConnected -> void Function(int fromWindowId)?

Callback fired when another window connects to this window (receives ipc-connect). Use it to set up stream subscriptions reactively.

onDisconnected -> void Function(int fromWindowId)?

Callback fired when a connected window sends a disconnect signal (on close or reuse-hide).

connectedWindowIds -> Set<int>

IDs of all windows this channel can currently send to.


IpcNotifierSender

Mixin for a ChangeNotifier that broadcasts field-level delta updates to other windows.

ipcSetup(WindowIpc<List<Object?>> ipc, String topic) -> void

Attaches the IPC channel and sets the routing topic. Multiple notifiers can share the same ipc with different topics.

ipcMark(String field, Object? value) -> void

Records that field changed to value and schedules a microtask flush. Call inside setters after updating the backing field and calling notifyListeners. Simultaneous changes within the same call stack are coalesced into one message.


IpcNotifierReceiver

Mixin for a ChangeNotifier that receives and applies delta updates from a source window.

ipcListen(WindowIpc<List<Object?>> ipc, int sourceId, String topic) -> void

Subscribes to updates tagged with topic from sourceId. May be called multiple times for different source windows.

ipcApplyField(String field, Object? value) -> void

Abstract. Override to apply a single incoming field change. notifyListeners is called once after all fields in the batch are applied.

ipcCancelListeners() -> void

Cancels all subscriptions. Call before dispose.


WindowRegistry

Accessible via MultiWindowManager.registry. Backed by shared native C++ state, so it is consistent across all Flutter engines in the process.

activeWindows -> ValueNotifier<List<int>>

IDs of currently visible / active windows. Updated automatically on every lifecycle event.

hiddenWindows -> ValueNotifier<List<int>>

IDs of reuse-cached hidden windows available for reclaiming. Updated automatically.

hasHiddenWindows -> bool

Whether at least one reusable window is available (reflects the last refresh result).

getActiveWindowIds() -> Future<List<int>>

Queries the native layer directly. Always returns a fresh value.

getHiddenWindowIds() -> Future<List<int>>

Queries the native layer directly. Always returns a fresh value.

refresh() -> Future<void>

Re-queries native state and updates activeWindows and hiddenWindows. Called automatically by MultiWindowManager on every lifecycle event.


License

MIT