Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

nativeapi for Dart and Flutter

Dart and Flutter bindings for nativeapi — unified access to native system APIs: windows, tray icons, menus, displays, keyboard, dialogs, storage and more.

Android iOS Linux macOS Windows
✅ ✅ ✅ ✅ ✅

🚧 Work in Progress: this package is under active development.

English | 简体中文

Packages

Package What it is
nativeapi The API: windows, tray icons, menus, displays, keyboard, dialogs, storage and more. Plain Dart, usable without Flutter.
cnativeapi Raw FFI bindings to core's C ABI; its build hook compiles core. Used by nativeapi.
nativeapi_flutter For Flutter apps: re-exports nativeapi and adds widgets, dart:ui conversions and the multi-window bridge.

Installation

A Flutter app:

flutter pub add nativeapi_flutter

A Dart app (command line, or any other Dart host):

dart pub add nativeapi

nativeapi has its own Point, Size, Rectangle and Color types. nativeapi_flutter converts them to and from dart:ui (window.bounds.toRect(), Offset(10, 20).toNative()), and leaves out the nativeapi names that Flutter already uses (Brightness, Color, Display, Image, ModifierKey, ShortcutManager, Size); import package:nativeapi/nativeapi.dart with a prefix to name one of those.

Quick Start

import 'package:nativeapi/nativeapi.dart';

for (final display in DisplayManager.instance.getAll()) {
  print('${display.name ?? ''}: ${display.size.width}x${display.size.height}');
}

Custom window chrome

With package:nativeapi_flutter/nativeapi_flutter.dart, wrap a custom title bar in DragToMoveArea to move the window by dragging (double tap to maximize/restore), and the window content in DragToResizeArea to resize from its edges and corners:

DragToResizeArea(
  resizeEdgeSize: 8,
  child: Column(
    children: [
      DragToMoveArea(
        child: SizedBox(height: 40, child: Center(child: Text('My window'))),
      ),
      Expanded(child: MyContent()),
    ],
  ),
)

Both widgets use WindowManager.instance.getCurrent() unless a window is passed. Moving via DragToMoveArea is not yet implemented on Linux.

Flutter-rendered secondary windows

Window.create() opens a bare native window with no Flutter view in it. To render widgets in a second window, create it with Flutter's multi-window API and hand the controller to nativeapi:

import 'package:flutter/src/foundation/_features.dart' show isWindowingEnabled;
import 'package:flutter/src/widgets/_window.dart' as fw;
import 'package:nativeapi_flutter/nativeapi_flutter.dart';
import 'package:nativeapi_flutter/windowing.dart';

// Before WidgetsFlutterBinding.ensureInitialized(): stable has no
// `flutter config --enable-windowing`, so the app switches the API on itself.
isWindowingEnabled = true;

final controller = fw.RegularWindowController(
  size: const Size(320, 48),
  title: 'Toolbar',
);

// In the widget tree: fw.RegularWindow(controller: controller, child: ...)

final window = controller.nativeWindow; // a nativeapi Window, same id as WindowManager's
window?.titleBarStyle = TitleBarStyle.hidden;
window?.isAlwaysOnTop = true;

All windows share one engine and one isolate, so they talk to each other through ordinary Dart objects — no runner changes, no message channels. Flutter's multi-window API is experimental and internal to the framework, which is why the bridge lives in its own library, package:nativeapi_flutter/windowing.dart. It is written against the stable channel (checked with 3.47.5); stable does not offer flutter config --enable-windowing, so the examples set Flutter's internal isWindowingEnabled in main(). See floating_toolbar_example for a child window built this way, and browser_tabs_example and detachable_window_example.

Windows and native views from plain Dart

A Dart program without Flutter can open windows too. Hand the app to runNativeApp(), which runs it on the platform's UI thread and keeps the platform loop turning between Dart's timers and futures:

import 'package:nativeapi/nativeapi.dart';

void app() {
  final window = Window.create()!..title = 'Hello';
  final root = window.contentView!
    ..layout = ViewLayout.column
    ..padding = const EdgeInsets(top: 16, right: 16, bottom: 16, left: 16)
    ..spacing = 8;
  final label = Label.create('Not clicked yet')!;
  var clicks = 0;
  final button = Button.create('Click me')!
    ..addListener((event) {
      if (event is ButtonClickedEvent) label.text = 'Clicked ${++clicks}×';
    });
  root
    ..addSubview(label)
    ..addSubview(button);
  window.show();
}

void main() => runNativeApp(app);

app must be a top-level or static function: it runs in an isolate of its own. On macOS that isolate lives on the process's first thread, the only one AppKit accepts, which the Dart VM otherwise keeps parked. The process ends when the app quits (Application.instance.quit(), Cmd+Q). See dart_view_example for a larger app built this way.

Examples

The examples are the flutter_* directories in the repository's examples/, plus dart_view_example, a plain Dart program (dart run bin/main.dart). Each Flutter one is an app for one module; they resolve through the pub workspace at the repository root:

flutter pub get          # at the repository root
cd examples/flutter_display_example
flutter run

Contributing

Development happens in nativeapi, which holds every binding and the code generator and checks out the core library as a submodule:

git clone --recursive https://github.com/libnativeapi/nativeapi.git

Files marked AUTO-GENERATED. DO NOT EDIT. are generated from the C++ headers in nativeapi. To change the API, send a pull request there; maintainers regenerate the bindings.

License

MIT