From bad316773588a9bae4f625f48bfa240fe9b44af5 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 15 Sep 2026 01:18:41 +0000 Subject: [PATCH 01/16] Draft Native Interop EAP documentation pages and restructure interop TOC - Add Native interop landing page with decision tree and layers guide - Add Pigeon guide covering platform channels and native bindings modes - Add FFIgen guide covering Apple frameworks, C-ABI, and developer flow - Add dart:ffi guide for native memory allocation and concurrency - Move bind-native-code into platform-integration/interop/ - Add JNIgen guide for Java/Kotlin and Android bindings - Update platform-channels to focus on manual message passing - Update sidenav default.yml with new Native interop section - Update firebase.json with 301 redirect for bind-native-code TAG=agy CONV=a76ca369-bcbf-4f8a-822b-b2d6bdd86f54 --- sites/docs/firebase.json | 1 + .../developing-packages.md | 2 +- .../platform-integration/android/setup.md | 2 +- .../src/content/platform-integration/index.md | 11 +- .../{ => interop}/bind-native-code.md | 2 +- .../platform-integration/interop/dart-ffi.md | 79 ++++++++ .../platform-integration/interop/ffigen.md | 128 +++++++++++++ .../platform-integration/interop/index.md | 171 ++++++++++++++++++ .../platform-integration/interop/jnigen.md | 157 ++++++++++++++++ .../platform-integration/interop/pigeon.md | 128 +++++++++++++ .../content/platform-integration/ios/setup.md | 2 +- .../platform-integration/legacy-ffi-plugin.md | 2 +- .../platform-integration/linux/building.md | 2 +- .../platform-integration/macos/setup.md | 2 +- .../platform-integration/platform-channels.md | 97 +++------- .../platform-integration/windows/building.md | 2 +- sites/docs/src/data/sidenav/default.yml | 26 ++- 17 files changed, 724 insertions(+), 90 deletions(-) rename sites/docs/src/content/platform-integration/{ => interop}/bind-native-code.md (99%) create mode 100644 sites/docs/src/content/platform-integration/interop/dart-ffi.md create mode 100644 sites/docs/src/content/platform-integration/interop/ffigen.md create mode 100644 sites/docs/src/content/platform-integration/interop/index.md create mode 100644 sites/docs/src/content/platform-integration/interop/jnigen.md create mode 100644 sites/docs/src/content/platform-integration/interop/pigeon.md diff --git a/sites/docs/firebase.json b/sites/docs/firebase.json index b03595ec854..523259afa76 100644 --- a/sites/docs/firebase.json +++ b/sites/docs/firebase.json @@ -196,6 +196,7 @@ { "source": "/platform-integration/android/install-android", "destination": "/platform-integration/android/setup", "type": 301 }, { "source": "/platform-integration/android/install-android/:rest*", "destination": "/platform-integration/android/setup", "type": 301 }, { "source": "/platform-integration/android/splash-screen-migration", "destination": "/release/breaking-changes/splash-screen-migration", "type": 301 }, + { "source": "/platform-integration/bind-native-code", "destination": "/platform-integration/interop/bind-native-code", "type": 301 }, { "source": "/platform-integration/c-interop", "destination": "/platform-integration/legacy-ffi-plugin", "type": 301 }, { "source": "/platform-integration/ios-app-clip", "destination": "/platform-integration/ios/ios-app-clip", "type": 301 }, { "source": "/platform-integration/ios/c-interop", "destination": "/platform-integration/legacy-ffi-plugin", "type": 301 }, diff --git a/sites/docs/src/content/packages-and-plugins/developing-packages.md b/sites/docs/src/content/packages-and-plugins/developing-packages.md index 60f183ae62d..21b3099524a 100644 --- a/sites/docs/src/content/packages-and-plugins/developing-packages.md +++ b/sites/docs/src/content/packages-and-plugins/developing-packages.md @@ -1036,7 +1036,7 @@ For additional details on `.podspec` files, see the All web dependencies are handled by the `pubspec.yaml` file, like any other Dart package. -[bind-native]: /platform-integration/bind-native-code +[bind-native]: /platform-integration/interop/bind-native-code [CocoaPods Documentation]: https://guides.cocoapods.org/syntax/podspec.html [Dart library package]: {{site.dart-site}}/guides/libraries/create-library-packages [`device_info_plus`]: {{site.pub-api}}/device_info_plus diff --git a/sites/docs/src/content/platform-integration/android/setup.md b/sites/docs/src/content/platform-integration/android/setup.md index f0e60c1e648..343189bb048 100644 --- a/sites/docs/src/content/platform-integration/android/setup.md +++ b/sites/docs/src/content/platform-integration/android/setup.md @@ -377,7 +377,7 @@ or begin improving integration with Android. Build and deploy to Android
  • - Bind to native Android code + Bind to native Android code
  • Add a splash screen diff --git a/sites/docs/src/content/platform-integration/index.md b/sites/docs/src/content/platform-integration/index.md index aee1e3d8b97..ac2407a77c5 100644 --- a/sites/docs/src/content/platform-integration/index.md +++ b/sites/docs/src/content/platform-integration/index.md @@ -22,8 +22,8 @@ For example, you might want to use a native library that's only available on iOS and iPadOS. For many use cases, you can find and use one of the many [Flutter plugins][] provided by the Flutter team and the amazing Flutter community. -If none of them meet your needs, you can -[write platform-specific code][] and even [create your own plugin][]. +If none of them meet your needs, you can explore [Native interop][], +[write platform-specific code][], or [create your own plugin][]. :::tip If you're exploring building your app for multiple platforms, @@ -32,6 +32,7 @@ also consider building your UI with [adaptive and responsive design][] in mind. [supported platforms]: /reference/supported-platforms [Flutter plugins]: /packages-and-plugins/using-packages +[Native interop]: /platform-integration/interop [write platform-specific code]: /platform-integration/platform-channels [create your own plugin]: /packages-and-plugins/developing-packages [adaptive and responsive design]: /ui/adaptive-responsive/ @@ -141,7 +142,7 @@ Learn how to add custom integrations with Android to your Flutter app.

    Learn how the latest Android APIs in your app from Dart.

    - +
    Bind to native code
    @@ -196,7 +197,7 @@ Learn how to add custom integrations with iOS to your Flutter app.

    Learn about plugins that support functionality from native iOS frameworks.

    - +
    Bind to native code
    @@ -317,7 +318,7 @@ Learn how to add custom integrations with Windows to your Flutter app. Learn how to add custom integrations with macOS to your Flutter app.
    - +
    Bind to native code
    diff --git a/sites/docs/src/content/platform-integration/bind-native-code.md b/sites/docs/src/content/platform-integration/interop/bind-native-code.md similarity index 99% rename from sites/docs/src/content/platform-integration/bind-native-code.md rename to sites/docs/src/content/platform-integration/interop/bind-native-code.md index 9ce23b731d2..1e33c8911e0 100644 --- a/sites/docs/src/content/platform-integration/bind-native-code.md +++ b/sites/docs/src/content/platform-integration/interop/bind-native-code.md @@ -18,7 +18,7 @@ Play services runtime on Android, use the standard plugin template (`flutter cre --template=plugin`). :::note -Looking for the previous FFI plugin workflow? See +Looking for the previous FFI plugin workflow? Consult [Bind to native code using the legacy FFI plugin template](/platform-integration/legacy-ffi-plugin), which documents the `plugin_ffi` template and OS-specific build files. ::: diff --git a/sites/docs/src/content/platform-integration/interop/dart-ffi.md b/sites/docs/src/content/platform-integration/interop/dart-ffi.md new file mode 100644 index 00000000000..3a8c3910c97 --- /dev/null +++ b/sites/docs/src/content/platform-integration/interop/dart-ffi.md @@ -0,0 +1,79 @@ +--- +title: Interact with native memory using dart:ffi +shortTitle: dart:ffi +description: Learn how to use dart:ffi to allocate native memory, work with pointers, and convert data types. +--- + +The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library +that provides low-level APIs to call C-ABI functions directly from Dart. + +Rather than a code generation tool like Pigeon, FFIgen, or JNIgen, +`dart:ffi` is the underlying runtime technology that powers these tools +and enables direct native memory interop. + +## When do you need dart:ffi + +When you use FFIgen or JNIgen to generate Dart bindings, +the generated code does not automatically convert Dart objects +(like `String` or `List`) into native formats. +Because Dart memory is automatically garbage collected and native C memory is +manually managed, you must use `dart:ffi` to bridge this gap. + +The two most common scenarios when `dart:ffi` is needed are: + +* **Memory allocation:** + Use `dart:ffi` to allocate space on the system heap for native arguments, + and manually free that memory when done to avoid memory leaks. +* **Data conversion:** + Use `dart:ffi` and helper extensions to convert Dart data types into native + representations (such as Dart `String` to UTF-8 C string pointers) + so they can be read by native code. + +## Core tools: dart:ffi and package:ffi + +To write low-level interop code, you use two complementary libraries: + +* **`dart:ffi`:** + Built into the core Dart SDK. + It provides native representation types (`Int32`, `Pointer`, `Float`, + `Struct`), lookup signatures, and dynamic library loading (`DynamicLibrary`). +* **`package:ffi`:** + A required package dependency when working with `dart:ffi`. + It provides memory allocators (`malloc`, `calloc`), extension methods, + and conversion helpers (such as `toNativeUtf8()` and `toDartString()`). + +## Concurrency in Flutter + +FFI calls are synchronous and run on the thread they are invoked on. +If you invoke a heavy or blocking C function on your Flutter app's main UI thread, +your app freezes or drops frames. + +To prevent UI jank, execute blocking FFI calls inside a background Dart isolate +using `Isolate.run`: + +```dart +import 'dart:ffi'; +import 'dart:isolate'; + +Future computeHeavyNativeTask() async { + return await Isolate.run(() { + // Synchronous FFI call executes in a background isolate + final result = nativeHeavyCalculation(); + return result; + }); +} +``` + +## Resources + +To explore examples of memory allocation, string conversion, and dynamic +library loading, refer to these Dart interop resources: + +* [C interop guide on dart.dev][dart-c-interop] + (includes code examples for pointers, memory allocation, and structs) +* [dart:ffi API reference][dart-ffi-api] +* [package:ffi on pub.dev][package-ffi-pub] + +[dart-c-interop]: {{site.dart-site}}/interop/c-interop +[dart-ffi-api]: {{site.dart.api}}/dart-ffi/dart-ffi-library.html +[package-ffi-pub]: https://pub.dev/packages/ffi diff --git a/sites/docs/src/content/platform-integration/interop/ffigen.md b/sites/docs/src/content/platform-integration/interop/ffigen.md new file mode 100644 index 00000000000..5384ca9547a --- /dev/null +++ b/sites/docs/src/content/platform-integration/interop/ffigen.md @@ -0,0 +1,128 @@ +--- +title: FFIgen +description: Learn how to use FFIgen to generate type-safe Dart bindings for C, Objective-C, and Swift APIs. +--- + +FFIgen (the Foreign Function Interface generator) is a code generator tool +that creates type-safe Dart bindings for C libraries and Apple frameworks +(Objective-C and Swift). +This allows you to call Apple platform APIs (iOS and macOS) directly and +synchronously from your Dart code. + +:::note +Swift interop is currently in experimental support. +::: + +## When to use FFIgen + +Use FFIgen when you need to call C libraries, wrap compiled binaries (like C, +C++, Rust, or Go), or interact directly with Objective-C and Swift frameworks +on iOS and macOS. + +:::note +FFIgen parses C and Objective-C headers using `libclang`. +To wrap C++ libraries, you must expose a C-compatible interface +(`extern "C"`) around your C++ code. +FFIgen cannot parse C++ classes or templates directly. +::: + +For standard, cross-platform mobile plugin development where you don't have +critical data transfers, consider using +[Pigeon](/platform-integration/interop/pigeon) instead. + +## Developer flow + +Generating bindings with FFIgen follows these steps: + +1. **Reference the native headers:** + Identify the native header files or APIs you want to wrap. +2. **Write the FFIgen configuration file:** + Create a configuration file (typically `ffigen.yaml`). + This configuration specifies where the header files are located and where + to output the generated Dart code: + + ```yaml + # ffigen.yaml + output: 'lib/src/generated_bindings.dart' + headers: + entry-points: + - 'src/my_header.h' + ``` + +3. **Generate bindings:** + Run the generator from your terminal: + + ```bash + dart run ffigen + ``` + +4. **Call the generated code from Dart:** + Import the generated Dart file into your app and call the functions + directly. + Because FFIgen communicates directly using the underlying FFI layer, + refer to the [dart:ffi guide](/platform-integration/interop/dart-ffi) + to learn how to manage native memory lifetimes and execute native calls + without blocking the UI thread. + +:::note +If the native API changes, update your configuration file and regenerate +the bindings. +::: + +## Examples + +To explore a complete implementation of this flow, including how to configure +the generator, consult the [official FFIgen examples on GitHub][ffigen-examples]. + +* **Add example:** Highlights the general flow with a toy native API + (one function in a single header file). +* **`code_assets` examples:** Highlight real-world examples like system + hostname lookups and audio playback. + +## Constraints and limitations + +### General considerations + +* **Source header files:** + FFIgen uses `libclang` to parse headers. Always resolve compiler warnings + logged during generation to prevent runtime crashes. +* **OS differences:** + Dart has no equivalent of C preprocessor macros (like `#if TARGET_OS_IPHONE`), + so you must handle minor API variations using runtime `Platform.isIOS` checks. + +### Objective-C and Swift pitfalls + +* **Objective-C bindings:** + Objective-C methods are dynamically resolved. + FFIgen automatically maps method selectors to Dart signatures, but you should + verify selectors by reading the bindings if you use heavily overloaded native + methods. +* **Objective-C memory management:** + Dart uses garbage collection while Objective-C uses reference counting. + Passing Dart closures that close over native wrapper objects can easily create + reference cycles and leak memory. +* **Objective-C runtime checks:** + FFIgen wrappers enforce Dart static types, but the underlying Objective-C + object might have a different runtime subclass, so Dart `'is'` and `'as'` + keywords won't work as expected. Use `.isA()` and `.as()` instead. +* **Objective-C multithreading:** + UI-related Objective-C calls must run on the iOS main platform thread. + Also, some types of callbacks can crash your app if executed on the wrong + thread. +* **Swift compatibility:** + FFIgen interacts with Swift through Objective-C. Pure Swift features + (such as structs, generics, or enums with associated values) are not + supported directly without a compatibility wrapper. + Classes must be annotated with `@objc` or inherit from `NSObject`. + For native Swift plugin authoring, consider + [Pigeon](/platform-integration/interop/pigeon). + +## Resources + +* [FFIgen package on pub.dev][ffigen-pub] +* [objective_c package on pub.dev][objc-pub] +* [Official FFIgen examples on GitHub][ffigen-examples] + +[ffigen-pub]: https://pub.dev/packages/ffigen +[objc-pub]: https://pub.dev/packages/objective_c +[ffigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/ffigen/example diff --git a/sites/docs/src/content/platform-integration/interop/index.md b/sites/docs/src/content/platform-integration/interop/index.md new file mode 100644 index 00000000000..040beea763a --- /dev/null +++ b/sites/docs/src/content/platform-integration/interop/index.md @@ -0,0 +1,171 @@ +--- +title: Native interop +description: Learn about the options to call platform-specific code in your app. +--- + +Flutter apps run on multiple platforms, +but sometimes you need to communicate directly +with the host operating system or run code from an existing native library. + +To solve this, Flutter provides several interop tools ranging from +code generators to direct FFI bindings. + +Use the following decision tree to choose your interop solution, +or learn more about the native interop layers. + +## Choose your interop solution + +```mermaid +flowchart TD + Start["What is your primary goal?"] --> GoalA["Platform integration
    (Swift, Kotlin, Java, Obj-C)"] + Start --> GoalB["Wrap native C-ABI library
    (C, Rust, Go)"] + Start --> GoalC["Standalone Dart package
    (No Flutter framework)"] + + GoalB --> FFIgen["Use FFIgen
    (or Flutter Rust Bridge)"] + GoalC --> Direct["Use FFIgen / JNIgen"] + + GoalA --> EvalPigeon{"Building a Flutter plugin?
    Evaluate constraints"} + EvalPigeon -->|"Standard API surface
    (Default / Recommended)"| Pigeon["Use Pigeon"] + EvalPigeon -->|"High-frequency data streaming,
    complex cross-language subclasses,
    or manual memory management"| DirectInterop["Use FFIgen / JNIgen"] +``` + +### Step 1: Determine your primary goal + +* **Goal A: Platform integration** + Communicate with native OS features (Swift, Kotlin, Java, Objective-C) + or expose an API surface. + ➡ (Proceed to Step 2) +* **Goal B: C-ABI wrapping** + Wrap an existing native library (C, Rust, Go). + 👉 Use [FFIgen](/platform-integration/interop/ffigen) + (or Flutter Rust Bridge for Rust). +* **Goal C: Pure Dart package** + Build a Dart CLI tool without Flutter framework dependencies. + 👉 Use [FFIgen](/platform-integration/interop/ffigen) or + [JNIgen](/platform-integration/interop/jnigen). + +### Step 2: Evaluate Pigeon for Flutter plugins + +If you are building a Flutter plugin, +first verify whether Pigeon satisfies your technical constraints. + +:::important +Migrating a plugin between Pigeon and FFIgen/JNIgen is non-trivial. +Choose your interop solution carefully based on your initial project constraints. +With Pigeon, you define APIs in Dart, generate native interfaces, and write +glue code in native languages (Kotlin, Swift, C++). +With FFIgen/JNIgen, you generate Dart APIs from native code and write glue code +in Dart. +Because glue code lives in completely different languages and architectural +layers, switching approaches requires rewriting the integration. +::: + +Do any of the following requirements apply to your feature? + +* Your app processes high-frequency raw data (e.g., video processing). +* Your integration requires advanced language bridging + (e.g., cross-language subclasses, delegates, or proxies). +* Your feature requires manual memory management and multithreading over + guardrails. +* Your target language is C, C++, Rust, or Go. + +**NO (Most common)** 👉 **USE [PIGEON](/platform-integration/interop/pigeon).** +This is the default, recommended path. +It provides necessary guardrails, creates easily digestible interface mappings, +and is highly efficient for most API surface areas. + +**YES** 👉 **USE [FFIgen](/platform-integration/interop/ffigen) / +[JNIgen](/platform-integration/interop/jnigen).** +You need the flexibility, complexity, and performance of direct interop for +larger surface areas. + +:::note +Directly using FFIgen and JNIgen introduces potential pitfalls, +particularly around threading and memory management. +Pigeon is generally recommended because it has fewer potential pitfalls +and provides safer guardrails. +::: + +## Packaging your interop code + +Depending on how you plan to share your code, +choose the option that best suits your use case. + +:::note +To learn about the difference between a package and a plugin, +refer to [Using packages](/packages-and-plugins/using-packages). +::: + +* **In-app integration** + Write your native interop code directly inside your Flutter app without + creating a separate package. +* **Plugin** + A specialized package that makes native platform functionality (such as a + device's camera or sensors) available to your app + (`flutter create --template=plugin`). + To learn more, refer to + [Developing plugin packages](/packages-and-plugins/developing-packages#plugin). +* **FFI package** + A package that compiles and bundles native C/C++ code using Dart build hooks + and `dart:ffi`. + This method also includes an `ffigen.yaml` configuration file to regenerate + Dart bindings (`flutter create --template=package_ffi`). + To learn more, refer to + [Developing FFI packages](/packages-and-plugins/developing-packages#ffi). + +:::note +If you're using pre-compiled binaries (such as `.so` files on Android or +`.dylib` files on iOS), use `CodeAsset` in your `hook/build.dart` script to +bundle them into your package. +::: + +For more details on package authoring, +refer to [Developing packages & plugins](/packages-and-plugins/developing-packages). + +## Tool comparison + +| Target language | Direct interop | Pigeon | +|---|---|---| +| **C** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended. Supported with platform channels, but not maintained by the Flutter team. | +| **C++ with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **C++ without `extern "C"`** | FFIgen (not yet supported) | Supported with platform channels, but not maintained by the Flutter team. | +| **Rust with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **Rust without `extern "C"`** | Flutter Rust Bridge | Not supported | +| **Go with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **Go without `extern "C"`** | Not supported | Not supported | +| **Java** | [JNIgen](/platform-integration/interop/jnigen) | Supported via Kotlin | +| **Kotlin** | [JNIgen](/platform-integration/interop/jnigen) | Supported | +| **Objective-C** | [FFIgen](/platform-integration/interop/ffigen) | Supported via Swift | +| **Swift without Obj-C compatibility annotations** | Swiftgen (experimental) | Supported | + +* Direct interop: Lower-level direct bindings, higher performance for large data, + requires manual memory and threading management. +* Pigeon: Structured type-safe code generator, recommended default for plugins. + +## Understanding interop layers + +Dart structures native interop into three distinct layers: +low-level bindings, idiomatic Dart APIs, and framework-specific plugins. + +For complete architectural details, +consult the [native interop layers guide](https://github.com/dart-lang/native/blob/main/doc/interop-layers.md). + +### Layer 1: Low-level bindings + +Layer 1 consists of thin, high-performance, and unopinionated wrappers around +native code (for example, `dart:ffi`, `dart:js_interop`, FFIgen, and JNIgen). +These APIs mirror native signatures closely but require manual memory and +threading management. + +### Layer 2: Idiomatic Dart APIs + +Layer 2 consists of idiomatic Dart APIs that wrap the low-level layer to hide +platform-specific complexities (for example, Pigeon or the `sqlite3` package). +These manage concurrency, memory lifetimes, and data conversions automatically. + +### Layer 3: Framework-specific plugins + +Layer 3 provides integration with framework-specific APIs (for example, the +Flutter Plugin API). +This layer exposes Dart APIs to the framework while hiding platform details +like Android's `Context` or iOS's application lifecycle. diff --git a/sites/docs/src/content/platform-integration/interop/jnigen.md b/sites/docs/src/content/platform-integration/interop/jnigen.md new file mode 100644 index 00000000000..cf417c676c9 --- /dev/null +++ b/sites/docs/src/content/platform-integration/interop/jnigen.md @@ -0,0 +1,157 @@ +--- +title: JNIgen +description: Learn how to use JNIgen to generate type-safe Dart bindings for Java and Kotlin APIs. +--- + +JNIgen (Java Native Interface generator) is a code generator tool that creates +type-safe Dart bindings for Java and Kotlin libraries and APIs. +This allows you to call Android APIs directly and synchronously from your +Dart code. + +## When to use JNIgen + +Use JNIgen when you need to call Java or Kotlin libraries, wrap compiled JAR +or AAR files, or interact directly with Android platform APIs from your Dart code. +It is most appropriate when you need to wrap existing third-party Java/Kotlin SDKs, +integrate complex Android system APIs without writing manual platform channel +handlers, or transfer high-throughput data to and from the JVM. + +Because JNIgen uses direct JNI bindings through `dart:ffi`, +it is also the recommended tool when you require high-performance, +low-latency interop, or need to pass massive amounts of raw data between Dart +and the JVM. + +For standard, cross-platform mobile plugin development where you don't have +critical data transfers, consider using +[Pigeon](/platform-integration/interop/pigeon) instead. + +## Developer flow + +Generating bindings with JNIgen follows this general flow: + +1. **Identify native classes or JARs:** + Identify the native Java/Kotlin classes, packages, or compiled `.jar`/`.aar` + files you want to wrap. + +2. **Write the JNIgen configuration file:** + Create a configuration file (typically `jnigen.yaml`): + + ```yaml + # jnigen.yaml + output: + dart: 'lib/src/generated/android_utils.dart' + classes: + - 'android.text.format.DateFormat' + ``` + + :::note + For a complete list of configuration options, + consult the [JNIgen documentation on pub.dev][jnigen-pub] and the + [JNIgen README][jnigen-readme]. + ::: + +3. **Generate bindings:** + Run the generator from your terminal: + + ```bash + dart run jnigen --config jnigen.yaml + ``` + +4. **Call the generated code from Dart:** + Import the generated Dart file into your app and call the functions + directly: + + ```dart + import 'src/generated/android_utils.dart'; + + void printFormattedDate() { + final formatted = DateFormat.format( + 'yyyy-MM-dd', + DateTime.now().millisecondsSinceEpoch, + ); + print('Formatted date: $formatted'); + } + ``` + + Because JNIgen communicates directly using the underlying FFI layer, + refer to the [dart:ffi guide](/platform-integration/interop/dart-ffi) + to learn how to manage native memory lifetimes and execute native calls + without blocking the UI thread. + +:::note +If the native API changes, update your configuration file and regenerate +the bindings. +::: + +## Examples + +To explore complete implementations of this flow, including how to configure +the generator, consult: + +* [`permissions_plugin` demo][permissions-demo] + in the official Flutter native interop demos repository +* [`android_toast_demo`][toast-demo] + in the official Flutter native interop demos repository +* [Official JNIgen examples on GitHub][jnigen-examples] + +## Constraints and limitations + +### Threading considerations + +JNIgen calls execute synchronously on the calling thread. +Watch out for two common scenarios: + +* **Platform UI thread affinity:** + Any Android API that interacts with Views, Activities, or UI components + must run on the Android main thread. + If invoking these from a background Dart isolate or worker thread, + dispatch the call to the Android main looper: + `Handler(Looper.getMainLooper()).post { ... }`. +* **Deadlock risk with reverse callbacks:** + Dart callbacks from Java run on the isolate that created them. + This can lead to deadlocks if a blocked Java thread waits for a blocked + Dart thread. + +### Android Context + +Many Android platform APIs require an application or activity `Context`. +When working with JNIgen in a Flutter Android app, you can obtain the global +application context through `Jni.androidApplicationContext` or retrieve the +current activity reference through `Jni.getCurrentActivity()`. + +### Lifecycle of Java objects + +JNI wrappers use Java global references. +Android has a hard limit of 50,000 active global references. +Call `.release()` or use `.using()` blocks to prevent memory leaks. +Use `WeakReference` to break reference cycles between Dart and Java garbage +collectors when implementing interfaces. + +### JVM runtime type checks + +JNI wrappers are Dart extension types, and they compile down to `JObject` +at runtime. +Because of this, Dart's native `'is'` and `'as'` keywords do not check the Java +runtime type. +Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. + +### General debugging + +Tracking down JNI memory allocation bugs like `DoubleReleaseError` and +`UseAfterReleaseError` can be difficult. +Set `Jni.captureStackTraceOnRelease = true` to log stack traces and help +trace leaks. + +## Resources + +* [JNIgen package on pub.dev][jnigen-pub] +* [JNI package on pub.dev][jni-pub] +* [JNIgen source code on GitHub][jnigen-readme] +* [Official JNIgen examples on GitHub][jnigen-examples] + +[jnigen-pub]: https://pub.dev/packages/jnigen +[jni-pub]: https://pub.dev/packages/jni +[jnigen-readme]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen +[jnigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example +[permissions-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/permissions_plugin +[toast-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/android_toast_demo diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/platform-integration/interop/pigeon.md new file mode 100644 index 00000000000..8b0d0f08dcc --- /dev/null +++ b/sites/docs/src/content/platform-integration/interop/pigeon.md @@ -0,0 +1,128 @@ +--- +title: Pigeon +description: Learn how to use Pigeon to generate type-safe bindings between your Dart code and the host platform. +--- + +Pigeon is the recommended default tool for building Flutter plugins. +Pigeon replaces the manual setup of raw platform channels with generated, +type-safe APIs. +This helps prevent typos in method names or mismatched argument types. + +## Choosing a communication option with Pigeon + +Pigeon supports two communication options depending on your target +platform and performance needs: +**native interop** (direct FFI and JNI) or **platform channels**. + +You can use both options simultaneously in the same plugin (for example, +generating native bindings for Android and iOS, +and platform channels for Windows and Linux). + +:::note +For low-level integrations that require direct access to native memory or +C/C++ libraries, use [FFIgen](/platform-integration/interop/ffigen) or +[JNIgen](/platform-integration/interop/jnigen) instead. +::: + +### Pigeon with native interop + +Pigeon with native interop generates direct native bindings using FFI or JNI +for mobile platforms (Android and iOS). + +Use this option if your use case requires any of the following: + +* You need synchronous execution on your native platform. +* You perform high-frequency data streaming (for example, audio or camera frames). +* You call native code from background Dart isolates. + +### Pigeon with platform channels + +Pigeon with platform channels generates standard platform channels that support +all Flutter platforms (Android, iOS, macOS, Windows, Linux, and Web). + +Use this option if your use case requires any of the following: + +* You target platforms that do not yet support direct native bindings + (Windows, Linux, or Web). +* You want zero external toolchains. + +## Developer flow + +The developer experience with Pigeon follows a structured cycle: + +1. **Define the schema:** + Define the communication contract in a Dart file (typically outside of your + `lib/` directory, such as `pigeons/messages.dart`). + This file defines data structures and API interfaces using Pigeon annotations + like `@HostApi` and `@FlutterApi`. + +2. **Generate the code:** + Run the Pigeon generator tool, which parses your definition file and produces + the necessary code for both Dart and your target native platforms: + + ```bash + dart run pigeon --input pigeons/messages.dart + ``` + + :::note + If you use Pigeon with native interop, set + `swiftOptions: SwiftOptions(useFfi: true)` and + `kotlinOptions: KotlinOptions(useJni: true)` in the `@ConfigurePigeon` + section of your schema. + For complete prerequisites and build configuration, + refer to the [Pigeon native interop guide][pigeon-native-guide]. + ::: + +3. **Implement the native interface:** + Implement the generated native interfaces in your host project (Swift, + Kotlin, Java, or C++) and register them with the Flutter engine. + +4. **Call the API from Flutter:** + Call the generated Dart classes from your Flutter application, + or invoke the generated native APIs to call back into your Dart code. + +If you need to modify the API, update the definition file, +regenerate the code, and then update your implementation. + +## Examples + +To explore implementations of this flow, including Dart definitions, +generated code, and native implementations, consult: + +* [Pigeon example app in the flutter/packages repository][pigeon-example] +* [Flutter native interop demos repository][native-interop-demos] + +## Constraints and limitations + +### High-frequency data + +For specialized use cases requiring direct manipulation of raw native pointers +or manual C/C++ memory management, use +[FFIgen](/platform-integration/interop/ffigen) or +[JNIgen](/platform-integration/interop/jnigen) instead. + +### Inheritance + +Pigeon does not support complex inheritance across data models. +Flatten your data models where possible. + +### Error handling + +* **Platform channels:** For `@async` methods, native code must return errors + through the provided callback handler. +* **Native bindings (FFI/JNI):** Throw native errors directly using standard + language features (`throw` in Swift `async throws` methods, + or throw exceptions in Kotlin `suspend` functions). + +## Resources + +* [Pigeon package on pub.dev][pigeon-pub] +* [Pigeon source code on GitHub][pigeon-source] +* [Pigeon native interop guide][pigeon-native-guide] +* [Flutter native interop demos][native-interop-demos] + +[pigeon-pub]: https://pub.dev/packages/pigeon +[pigeon-source]: https://github.com/flutter/packages/tree/main/packages/pigeon +[pigeon-example]: https://github.com/flutter/packages/tree/main/packages/pigeon/example +[pigeon-native-guide]: https://github.com/flutter/packages/blob/main/packages/pigeon/native_interop_guide.md +[native-interop-demos]: https://github.com/flutter/demos/tree/main/native_interop_demos diff --git a/sites/docs/src/content/platform-integration/ios/setup.md b/sites/docs/src/content/platform-integration/ios/setup.md index 1fdd4901167..b537c06c8ad 100644 --- a/sites/docs/src/content/platform-integration/ios/setup.md +++ b/sites/docs/src/content/platform-integration/ios/setup.md @@ -268,7 +268,7 @@ or begin improving integration with iOS.
    Build and deploy to iOS
  • - Bind to native iOS code + Bind to native iOS code
  • Leverage system frameworks diff --git a/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md b/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md index ee0580c7a8f..e0c7e7bdc5c 100644 --- a/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md +++ b/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md @@ -25,7 +25,7 @@ _FFI_ stands for [_foreign function interface._][FFI] Other terms for similar functionality include _native interface_ and _language bindings._ -[build hooks]: /platform-integration/bind-native-code +[build hooks]: /platform-integration/interop/bind-native-code [`dart:ffi`]: {{site.dart.api}}/dart-ffi/dart-ffi-library.html [FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface diff --git a/sites/docs/src/content/platform-integration/linux/building.md b/sites/docs/src/content/platform-integration/linux/building.md index 8fb102e5ee0..bb997e0702f 100644 --- a/sites/docs/src/content/platform-integration/linux/building.md +++ b/sites/docs/src/content/platform-integration/linux/building.md @@ -40,7 +40,7 @@ including common packages such as [`url_launcher`], [`shared_preferences`], [`file_selector`], and [`path_provider`]. [C interop using `dart:ffi`]: {{site.dart-site}}/guides/libraries/c-interop -[Bind to native code using FFI]: /platform-integration/bind-native-code +[Bind to native code using FFI]: /platform-integration/interop/bind-native-code [Canonical]: {{site.pub}}/publishers/canonical.com/packages [support-linux]: {{site.pub}}/packages?q=platform%3Alinux [`url_launcher`]: {{site.pub-pkg}}/url_launcher diff --git a/sites/docs/src/content/platform-integration/macos/setup.md b/sites/docs/src/content/platform-integration/macos/setup.md index df2ffe27284..2562690644f 100644 --- a/sites/docs/src/content/platform-integration/macos/setup.md +++ b/sites/docs/src/content/platform-integration/macos/setup.md @@ -169,7 +169,7 @@ or begin expanding integration with macOS. Build and deploy to macOS
  • - Bind to native macOS code + Bind to native macOS code
  • Embed native macOS views diff --git a/sites/docs/src/content/platform-integration/platform-channels.md b/sites/docs/src/content/platform-integration/platform-channels.md index 310a6f42b0a..319b2a64b55 100644 --- a/sites/docs/src/content/platform-integration/platform-channels.md +++ b/sites/docs/src/content/platform-integration/platform-channels.md @@ -1,26 +1,37 @@ --- -title: Writing custom platform-specific code -shortTitle: Platform-specific code -description: Learn how to write custom platform-specific code in your app. +title: Write platform-specific code with platform channels +shortTitle: Platform channels +description: Learn how to write platform-specific code using Flutter's platform channels. --- -This guide describes how to use custom platform-specific -code with Flutter. +:::note +Refer to the [Native interop landing page](/platform-integration/interop) +for an overview and recommended approaches for calling native platform code. +::: + +Platform channels pass messages between Flutter and your host platform +(Android, iOS, macOS, Windows, Linux, or Web). + +While [Pigeon](/platform-integration/interop/pigeon), +[FFIgen](/platform-integration/interop/ffigen), and +[JNIgen](/platform-integration/interop/jnigen) are recommended for new projects, +platform channels remain available for low-level message passing and +maintaining existing codebases. ## Overview You can use platform-specific code in your Flutter app. -A few common ways to do this include: +Common ways to do this include: * Use Flutter's platform channel APIs to pass messages between Flutter and your desired platforms. For more - information, see [Call platform-specific code using platform channels](#example). + details, refer to [Call platform-specific code using platform channels](#example). * Use the `Pigeon` package to generate type-safe platform-specific code. For more - information, see [Call platform-specific code using the Pigeon package](#pigeon). + details, refer to [Pigeon](/platform-integration/interop/pigeon). Flutter supports the following platforms and platform-specific languages: @@ -1130,71 +1141,11 @@ it displays 'Battery level not available'. ## Call platform-specific code using the Pigeon package {:#pigeon} -You can use the [`Pigeon`][] package as -an alternative to Flutter's platform channel APIs -to generate code that sends messages in a -structured, type-safe manner. The workflow for Pigeon -looks like this: - - * The Flutter app sends structured - type-safe messages to its _host_, the non-Dart portion - of the app, over a platform channel. - - * The _host_ listens on the platform channel, and receives - the message. It then calls into any number of - platform-specific APIs using the native programming - language and sends a response back to the _client_, - the Flutter portion of the app. - -Using this package eliminates the need to match -strings between host and client for the names and -data types of messages. It supports nested classes, -grouping messages into APIs, generation of asynchronous -wrapper code, and sending messages in either direction. The -generated code is readable and guarantees there are no -conflicts between multiple clients of different versions. - -With Pigeon, the messaging protocol is defined -in a subset of Dart that then generates messaging -code for Android, iOS, macOS, or Windows. For example: - - -```dart title="pigeon_source.dart" -import 'package:pigeon/pigeon.dart'; - -class SearchRequest { - final String query; - - SearchRequest({required this.query}); -} - -class SearchReply { - final String result; - - SearchReply({required this.result}); -} - -@HostApi() -abstract class Api { - @async - SearchReply search(SearchRequest request); -} -``` - - -```dart title="use_pigeon.dart" -import 'generated_pigeon.dart'; - -Future onClick() async { - SearchRequest request = SearchRequest(query: 'test'); - Api api = SomeApi(); - SearchReply reply = await api.search(request); - print('reply: ${reply.result}'); -} -``` - -You can find a complete example and more information -on the [`pigeon`][] page on pub.dev. +Pigeon is the recommended default tool for generating type-safe bindings +between Dart and host platforms. +To learn how to use Pigeon, define API contracts, and choose between +native interop and platform channels, +refer to the dedicated [Pigeon guide](/platform-integration/interop/pigeon). ## Channels and platform threading diff --git a/sites/docs/src/content/platform-integration/windows/building.md b/sites/docs/src/content/platform-integration/windows/building.md index 6c13855f43d..b79072ecf0f 100644 --- a/sites/docs/src/content/platform-integration/windows/building.md +++ b/sites/docs/src/content/platform-integration/windows/building.md @@ -47,7 +47,7 @@ More generally, many other [packages support Windows], including common packages such as [`url_launcher`], [`shared_preferences`], [`file_selector`], and [`path_provider`]. [C interop using `dart:ffi`]: {{site.dart-site}}/guides/libraries/c-interop -[Bind to native code using FFI]: /platform-integration/bind-native-code +[Bind to native code using FFI]: /platform-integration/interop/bind-native-code [win32 package]: {{site.pub}}/packages/win32 [Windows registry]: {{site.pub}}/packages/win32_registry [gamepad support]: {{site.pub}}/packages/win32_gamepad diff --git a/sites/docs/src/data/sidenav/default.yml b/sites/docs/src/data/sidenav/default.yml index 31cec95cd1c..0652d095d6f 100644 --- a/sites/docs/src/data/sidenav/default.yml +++ b/sites/docs/src/data/sidenav/default.yml @@ -361,6 +361,28 @@ - title: Design patterns permalink: /app-architecture/design-patterns +- title: Native interop + permalink: /platform-integration/interop + icon: alt_route + children: + - title: Overview + permalink: /platform-integration/interop + - title: Pigeon + permalink: /platform-integration/interop/pigeon + - title: FFIgen + permalink: /platform-integration/interop/ffigen + children: + - title: Overview + permalink: /platform-integration/interop/ffigen + - title: "dart:ffi" + permalink: /platform-integration/interop/dart-ffi + - title: Bind to native code + permalink: /platform-integration/interop/bind-native-code + - title: JNIgen + permalink: /platform-integration/interop/jnigen + - title: Platform channels + permalink: /platform-integration/platform-channels + - title: Platform integration permalink: /platform-integration icon: integration_instructions @@ -369,10 +391,6 @@ permalink: /reference/supported-platforms - title: Build desktop apps with Flutter permalink: /platform-integration/desktop - - title: Write platform-specific code - permalink: /platform-integration/platform-channels - - title: Bind to native code - permalink: /platform-integration/bind-native-code - title: Android permalink: /platform-integration/android children: From 500b3f4ed21d5113fdf2a489676d6791c7a9f455 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 15 Sep 2026 01:26:55 +0000 Subject: [PATCH 02/16] Align interop markdown files 1:1 with Google Doc copy - Restore exact text, structure, and headings from the Google Doc - Remove extraneous code examples from developer flow sections TAG=agy CONV=a76ca369-bcbf-4f8a-822b-b2d6bdd86f54 --- .../platform-integration/interop/dart-ffi.md | 79 +++------ .../platform-integration/interop/ffigen.md | 163 ++++++++---------- .../platform-integration/interop/jnigen.md | 142 +++++---------- .../platform-integration/interop/pigeon.md | 112 +++++------- 4 files changed, 194 insertions(+), 302 deletions(-) diff --git a/sites/docs/src/content/platform-integration/interop/dart-ffi.md b/sites/docs/src/content/platform-integration/interop/dart-ffi.md index 3a8c3910c97..d9ef0bf7b75 100644 --- a/sites/docs/src/content/platform-integration/interop/dart-ffi.md +++ b/sites/docs/src/content/platform-integration/interop/dart-ffi.md @@ -1,76 +1,53 @@ --- title: Interact with native memory using dart:ffi shortTitle: dart:ffi -description: Learn how to use dart:ffi to allocate native memory, work with pointers, and convert data types. +description: Learn how to interact with native memory using the dart:ffi library. --- -The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library -that provides low-level APIs to call C-ABI functions directly from Dart. +The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library that +provides the low-level APIs to call C-ABI functions from Dart. -Rather than a code generation tool like Pigeon, FFIgen, or JNIgen, -`dart:ffi` is the underlying runtime technology that powers these tools -and enables direct native memory interop. +Rather than a code generation tool like Pigeon, FFIgen, or JNIgen, `dart:ffi` is the +underlying technology that powers these tools and enables native memory interop. ## When do you need dart:ffi -When you use FFIgen or JNIgen to generate Dart bindings, -the generated code does not automatically convert Dart objects -(like `String` or `List`) into native formats. -Because Dart memory is automatically garbage collected and native C memory is -manually managed, you must use `dart:ffi` to bridge this gap. +When you use FFIgen or JNIgen to generate Dart bindings, the generated code does not +automatically convert Dart objects like Strings or Lists into native formats. +Because Dart memory is automatically garbage collected and native C memory is manually +managed, you must use `dart:ffi` to bridge this gap. -The two most common scenarios when `dart:ffi` is needed are: +The two most common scenarios when `dart:ffi` is needed are memory allocation and data conversion. -* **Memory allocation:** - Use `dart:ffi` to allocate space on the system heap for native arguments, - and manually free that memory when done to avoid memory leaks. -* **Data conversion:** - Use `dart:ffi` and helper extensions to convert Dart data types into native - representations (such as Dart `String` to UTF-8 C string pointers) - so they can be read by native code. +With memory allocation, you use `dart:ffi` to allocate space on the system heap for +native arguments, and you must manually free that memory when done. This helps avoid +memory leaks. + +With data conversion, you use `dart:ffi` to convert Dart data types so they can be read +by native code. ## Core tools: dart:ffi and package:ffi -To write low-level interop code, you use two complementary libraries: +To write interop code, you use two libraries: -* **`dart:ffi`:** - Built into the core Dart SDK. - It provides native representation types (`Int32`, `Pointer`, `Float`, - `Struct`), lookup signatures, and dynamic library loading (`DynamicLibrary`). -* **`package:ffi`:** - A required package dependency when working with `dart:ffi`. - It provides memory allocators (`malloc`, `calloc`), extension methods, - and conversion helpers (such as `toNativeUtf8()` and `toDartString()`). +* **`dart:ffi`:** This is built into the core Dart SDK. It provides native representation + types and lookup signatures. +* **`package:ffi`:** This package is a required dependency for working with `dart:ffi`. + It provides memory allocation methods and conversion helpers. ## Concurrency in Flutter -FFI calls are synchronous and run on the thread they are invoked on. -If you invoke a heavy or blocking C function on your Flutter app's main UI thread, -your app freezes or drops frames. - -To prevent UI jank, execute blocking FFI calls inside a background Dart isolate -using `Isolate.run`: - -```dart -import 'dart:ffi'; -import 'dart:isolate'; - -Future computeHeavyNativeTask() async { - return await Isolate.run(() { - // Synchronous FFI call executes in a background isolate - final result = nativeHeavyCalculation(); - return result; - }); -} -``` +FFI calls are synchronous and run on the thread they are called on. If you invoke a heavy +or blocking C function on your Flutter app's main UI thread, your app will freeze or drop +frames. To prevent this, you should execute blocking FFI calls inside a background Dart +isolate using `Isolate.run`. ## Resources -To explore examples of memory allocation, string conversion, and dynamic -library loading, refer to these Dart interop resources: +To explore examples of memory allocation, string conversion, and dynamic library loading, +refer to these Dart interop resources: -* [C interop guide on dart.dev][dart-c-interop] - (includes code examples for pointers, memory allocation, and structs) +* [C interop guide (includes code examples for pointers, memory allocation, and structs)][dart-c-interop] * [dart:ffi API reference][dart-ffi-api] * [package:ffi on pub.dev][package-ffi-pub] diff --git a/sites/docs/src/content/platform-integration/interop/ffigen.md b/sites/docs/src/content/platform-integration/interop/ffigen.md index 5384ca9547a..581b91761ca 100644 --- a/sites/docs/src/content/platform-integration/interop/ffigen.md +++ b/sites/docs/src/content/platform-integration/interop/ffigen.md @@ -3,11 +3,12 @@ title: FFIgen description: Learn how to use FFIgen to generate type-safe Dart bindings for C, Objective-C, and Swift APIs. --- -FFIgen (the Foreign Function Interface generator) is a code generator tool -that creates type-safe Dart bindings for C libraries and Apple frameworks -(Objective-C and Swift). -This allows you to call Apple platform APIs (iOS and macOS) directly and -synchronously from your Dart code. +## Overview + +FFIgen (the Foreign Function Interface generator) is a code generator tool that creates +type-safe Dart bindings for C libraries and Apple frameworks (Objective-C and Swift). +This allows you to call Apple platform APIs (iOS and macOS) directly and synchronously +from your Dart code. :::note Swift interop is currently in experimental support. @@ -15,107 +16,95 @@ Swift interop is currently in experimental support. ## When to use FFIgen -Use FFIgen when you need to call C libraries, wrap compiled binaries (like C, -C++, Rust, or Go), or interact directly with Objective-C and Swift frameworks -on iOS and macOS. +Use FFIgen when you need to call C libraries, wrap compiled binaries (like C, C++, +Rust, or Go), or interact directly with Objective-C and Swift frameworks on iOS and macOS. :::note -FFIgen parses C and Objective-C headers using `libclang`. -To wrap C++ libraries, you must expose a C-compatible interface -(`extern "C"`) around your C++ code. -FFIgen cannot parse C++ classes or templates directly. +FFIgen parses C and Objective-C headers. To wrap C++ libraries, you must expose a +C-compatible interface around your C++ code. FFIgen cannot parse C++ classes or +templates directly. ::: For standard, cross-platform mobile plugin development where you don't have -critical data transfers, consider using -[Pigeon](/platform-integration/interop/pigeon) instead. +critical data transfers, consider using [Pigeon](/platform-integration/interop/pigeon) instead. ## Developer flow Generating bindings with FFIgen follows these steps: -1. **Reference the native headers:** - Identify the native header files or APIs you want to wrap. -2. **Write the FFIgen configuration file:** - Create a configuration file (typically `ffigen.yaml`). - This configuration specifies where the header files are located and where - to output the generated Dart code: - - ```yaml - # ffigen.yaml - output: 'lib/src/generated_bindings.dart' - headers: - entry-points: - - 'src/my_header.h' - ``` - -3. **Generate bindings:** - Run the generator from your terminal: - - ```bash - dart run ffigen - ``` - -4. **Call the generated code from Dart:** - Import the generated Dart file into your app and call the functions - directly. - Because FFIgen communicates directly using the underlying FFI layer, - refer to the [dart:ffi guide](/platform-integration/interop/dart-ffi) - to learn how to manage native memory lifetimes and execute native calls - without blocking the UI thread. +1. Reference the native headers. +2. Write the FFIgen configuration file. +3. Generate the bindings by running FFIgen. +4. Call the generated code from Dart. + +To start, identify the native header files or APIs you want to wrap. Then write a +configuration file (typically `ffigen.yaml` or `tool/ffigen.dart`). This configuration specifies +where the header files are located and where to output the generated Dart code. + +Next, run the generator tool (such as `dart run ffigen`), which parses the headers and +generates the corresponding Dart bindings. + +Last, import the generated Dart file into your app and call the functions directly. +Because FFIgen communicates directly using the underlying FFI layer, refer to the +[dart:ffi guide](/platform-integration/interop/dart-ffi) to learn how to manage native +memory lifetimes and execute native calls without blocking the UI thread. :::note -If the native API changes, update your configuration file and regenerate -the bindings. +If the native API changes, you need to update your configuration script and regenerate the bindings. ::: -## Examples +## Example + +To explore a complete implementation of this flow, including how to configure the +generator, check out the [official FFIgen examples][ffigen-examples]. -To explore a complete implementation of this flow, including how to configure -the generator, consult the [official FFIgen examples on GitHub][ffigen-examples]. +The Add example highlights the general flow with a toy native API (one function in a single header file). -* **Add example:** Highlights the general flow with a toy native API - (one function in a single header file). -* **`code_assets` examples:** Highlight real-world examples like system - hostname lookups and audio playback. +The `code_assets` examples highlight real-world examples like system hostname lookups and audio playback. ## Constraints and limitations -### General considerations - -* **Source header files:** - FFIgen uses `libclang` to parse headers. Always resolve compiler warnings - logged during generation to prevent runtime crashes. -* **OS differences:** - Dart has no equivalent of C preprocessor macros (like `#if TARGET_OS_IPHONE`), - so you must handle minor API variations using runtime `Platform.isIOS` checks. - -### Objective-C and Swift pitfalls - -* **Objective-C bindings:** - Objective-C methods are dynamically resolved. - FFIgen automatically maps method selectors to Dart signatures, but you should - verify selectors by reading the bindings if you use heavily overloaded native - methods. -* **Objective-C memory management:** - Dart uses garbage collection while Objective-C uses reference counting. - Passing Dart closures that close over native wrapper objects can easily create - reference cycles and leak memory. -* **Objective-C runtime checks:** - FFIgen wrappers enforce Dart static types, but the underlying Objective-C - object might have a different runtime subclass, so Dart `'is'` and `'as'` - keywords won't work as expected. Use `.isA()` and `.as()` instead. -* **Objective-C multithreading:** - UI-related Objective-C calls must run on the iOS main platform thread. - Also, some types of callbacks can crash your app if executed on the wrong - thread. -* **Swift compatibility:** - FFIgen interacts with Swift through Objective-C. Pure Swift features - (such as structs, generics, or enums with associated values) are not - supported directly without a compatibility wrapper. - Classes must be annotated with `@objc` or inherit from `NSObject`. - For native Swift plugin authoring, consider - [Pigeon](/platform-integration/interop/pigeon). +Some general considerations when working with FFIgen: + +### Source header files + +FFIgen uses `libclang` to parse headers. Always resolve compiler warnings logged during +generation to prevent runtime crashes. + +### OS differences + +Dart has no equivalent of C macros (like `#if TARGET_OS_IPHONE`), so you must handle minor +API variations using runtime `Platform.isIOS` checks. + +Some Objective-C and Swift-specific pitfalls when working with FFIgen: + +### Objective-C bindings + +Objective-C methods are dynamically resolved. FFIgen automatically maps method selectors +to Dart signatures, but you should verify selectors by reading the bindings if you are +using heavily overloaded native methods. + +### Objective-C memory management + +Dart uses garbage collection while Objective-C uses reference counting. Passing Dart closures +that close over native wrapper objects can easily create reference cycles and leak memory. + +### Objective-C runtime checks + +FFIgen wrappers enforce Dart static types, but the underlying Objective-C object might have +a different runtime subclass, so Dart 'is' and 'as' keywords won't work as expected. +Use `.isA()` and `.as()` instead. + +### Objective-C multithreading + +UI-related Objective-C calls must run on the iOS main platform thread. Also, some types +of callbacks can crash your app if they are executed on the wrong thread. + +### Swift compatibility + +Consider using the experimental Swiftgen package for this use case. FFIgen interacts with +Swift through Objective-C. Pure Swift features (such as structs, generics, or enums +associated with values) are not supported. Classes must be annotated with `@objc`. ## Resources diff --git a/sites/docs/src/content/platform-integration/interop/jnigen.md b/sites/docs/src/content/platform-integration/interop/jnigen.md index cf417c676c9..44c09a14619 100644 --- a/sites/docs/src/content/platform-integration/interop/jnigen.md +++ b/sites/docs/src/content/platform-integration/interop/jnigen.md @@ -3,18 +3,16 @@ title: JNIgen description: Learn how to use JNIgen to generate type-safe Dart bindings for Java and Kotlin APIs. --- +## Overview + JNIgen (Java Native Interface generator) is a code generator tool that creates type-safe Dart bindings for Java and Kotlin libraries and APIs. -This allows you to call Android APIs directly and synchronously from your -Dart code. +This allows you to call Android APIs directly and synchronously from your Dart code. ## When to use JNIgen Use JNIgen when you need to call Java or Kotlin libraries, wrap compiled JAR -or AAR files, or interact directly with Android platform APIs from your Dart code. -It is most appropriate when you need to wrap existing third-party Java/Kotlin SDKs, -integrate complex Android system APIs without writing manual platform channel -handlers, or transfer high-throughput data to and from the JVM. +or AAR files, or interact directly with Android from your Dart code. Because JNIgen uses direct JNI bindings through `dart:ffi`, it is also the recommended tool when you require high-performance, @@ -22,136 +20,84 @@ low-latency interop, or need to pass massive amounts of raw data between Dart and the JVM. For standard, cross-platform mobile plugin development where you don't have -critical data transfers, consider using -[Pigeon](/platform-integration/interop/pigeon) instead. +critical data transfers, consider using [Pigeon](/platform-integration/interop/pigeon) instead. ## Developer flow Generating bindings with JNIgen follows this general flow: -1. **Identify native classes or JARs:** - Identify the native Java/Kotlin classes, packages, or compiled `.jar`/`.aar` - files you want to wrap. - -2. **Write the JNIgen configuration file:** - Create a configuration file (typically `jnigen.yaml`): - - ```yaml - # jnigen.yaml - output: - dart: 'lib/src/generated/android_utils.dart' - classes: - - 'android.text.format.DateFormat' - ``` - - :::note - For a complete list of configuration options, - consult the [JNIgen documentation on pub.dev][jnigen-pub] and the - [JNIgen README][jnigen-readme]. - ::: - -3. **Generate bindings:** - Run the generator from your terminal: - - ```bash - dart run jnigen --config jnigen.yaml - ``` - -4. **Call the generated code from Dart:** - Import the generated Dart file into your app and call the functions - directly: - - ```dart - import 'src/generated/android_utils.dart'; - - void printFormattedDate() { - final formatted = DateFormat.format( - 'yyyy-MM-dd', - DateTime.now().millisecondsSinceEpoch, - ); - print('Formatted date: $formatted'); - } - ``` - - Because JNIgen communicates directly using the underlying FFI layer, - refer to the [dart:ffi guide](/platform-integration/interop/dart-ffi) - to learn how to manage native memory lifetimes and execute native calls - without blocking the UI thread. +1. Reference the native classes or JARs. +2. Write the JNIgen configuration file. +3. Generate the bindings by running JNIgen. +4. Call the generated code from Dart. :::note -If the native API changes, update your configuration file and regenerate -the bindings. +For a complete list of configuration options, consult the JNIgen documentation +on pub.dev and the JNIgen README. ::: -## Examples +To start, identify the native Java classes, packages, or compiled JAR/AAR files +you want to wrap. Then write a configuration file (typically `jnigen.yaml` or +`tool/jnigen.dart`). This configuration specifies where the native files are +located, what packages to include, and where to output the generated Dart code. -To explore complete implementations of this flow, including how to configure -the generator, consult: +Next, run the generator tool (such as `dart run jnigen --config jnigen.yaml`), +which parses the classes and generates the corresponding Dart bindings. -* [`permissions_plugin` demo][permissions-demo] - in the official Flutter native interop demos repository -* [`android_toast_demo`][toast-demo] - in the official Flutter native interop demos repository -* [Official JNIgen examples on GitHub][jnigen-examples] +Last, import the generated Dart file into your app and call the functions directly. +Because JNIgen communicates directly using the underlying FFI layer, +refer to the [dart:ffi guide](/platform-integration/interop/dart-ffi) to learn +how to manage native memory lifetimes and execute native calls without blocking +the UI thread. + +:::note +If the native API changes, you need to update your configuration script and +regenerate the bindings. +::: + +## Examples + +To explore complete implementations of this flow, including how to configure the +generator, check out the [permissions_plugin demo][permissions-demo] and the +[android_toast_demo][toast-demo] in the official Flutter native interop demos repository. ## Constraints and limitations +There are some general considerations and limitations when working with JNIgen. + ### Threading considerations JNIgen calls execute synchronously on the calling thread. Watch out for two common scenarios: -* **Platform UI thread affinity:** - Any Android API that interacts with Views, Activities, or UI components - must run on the Android main thread. - If invoking these from a background Dart isolate or worker thread, - dispatch the call to the Android main looper: - `Handler(Looper.getMainLooper()).post { ... }`. -* **Deadlock risk with reverse callbacks:** - Dart callbacks from Java run on the isolate that created them. - This can lead to deadlocks if a blocked Java thread waits for a blocked - Dart thread. - -### Android Context - -Many Android platform APIs require an application or activity `Context`. -When working with JNIgen in a Flutter Android app, you can obtain the global -application context through `Jni.androidApplicationContext` or retrieve the -current activity reference through `Jni.getCurrentActivity()`. +* Calling platform APIs that must run on the platform thread. Use `runOnPlatformThread` from `dart:ui`. +* Dart callbacks from Java run on the isolate that created them. This can lead to deadlocks if a blocked Java thread waits for a blocked Dart thread. ### Lifecycle of Java objects -JNI wrappers use Java global references. -Android has a hard limit of 50,000 active global references. -Call `.release()` or use `.using()` blocks to prevent memory leaks. -Use `WeakReference` to break reference cycles between Dart and Java garbage -collectors when implementing interfaces. +JNI wrappers use Java global references. There is a hard limit of 50,000 active references. +You should call `.release()` or use `.using()` blocks to prevent memory leaking. +Use `WeakReference` to break reference cycles between Dart and Java GCs when implementing interfaces. ### JVM runtime type checks -JNI wrappers are Dart extension types, and they compile down to `JObject` -at runtime. -Because of this, Dart's native `'is'` and `'as'` keywords do not check the Java -runtime type. +JNI wrappers are Dart extension types, and they compile down to `JObject` at runtime. +Because of this, Dart's native 'is' and 'as' keywords do not check the Java runtime type. Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. ### General debugging -Tracking down JNI memory allocation bugs like `DoubleReleaseError` and -`UseAfterReleaseError` can be difficult. -Set `Jni.captureStackTraceOnRelease = true` to log stack traces and help -trace leaks. +Tracking down JNI memory allocation bugs like `DoubleReleaseError` and `UseAfterReleaseError` can be difficult. +Set `Jni.captureStackTraceOnRelease = true` to log stack traces and help trace leaks. ## Resources * [JNIgen package on pub.dev][jnigen-pub] * [JNI package on pub.dev][jni-pub] -* [JNIgen source code on GitHub][jnigen-readme] * [Official JNIgen examples on GitHub][jnigen-examples] [jnigen-pub]: https://pub.dev/packages/jnigen [jni-pub]: https://pub.dev/packages/jni -[jnigen-readme]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen [jnigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example [permissions-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/permissions_plugin [toast-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/android_toast_demo diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/platform-integration/interop/pigeon.md index 8b0d0f08dcc..c08078b77e1 100644 --- a/sites/docs/src/content/platform-integration/interop/pigeon.md +++ b/sites/docs/src/content/platform-integration/interop/pigeon.md @@ -3,122 +3,102 @@ title: Pigeon description: Learn how to use Pigeon to generate type-safe bindings between your Dart code and the host platform. --- +## Overview + Pigeon is the recommended default tool for building Flutter plugins. Pigeon replaces the manual setup of raw platform channels with generated, -type-safe APIs. -This helps prevent typos in method names or mismatched argument types. +type-safe APIs. This helps prevent typos in method names or mismatched argument types. ## Choosing a communication option with Pigeon -Pigeon supports two communication options depending on your target -platform and performance needs: -**native interop** (direct FFI and JNI) or **platform channels**. - -You can use both options simultaneously in the same plugin (for example, -generating native bindings for Android and iOS, -and platform channels for Windows and Linux). +Pigeon supports the following two communication options depending on your target +platform and performance needs: native interop (direct FFI and JNI) or platform channels. :::note -For low-level integrations that require direct access to native memory or -C/C++ libraries, use [FFIgen](/platform-integration/interop/ffigen) or -[JNIgen](/platform-integration/interop/jnigen) instead. +For low-level integrations that require direct access to native memory or C/C++ libraries, +use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. ::: ### Pigeon with native interop -Pigeon with native interop generates direct native bindings using FFI or JNI -for mobile platforms (Android and iOS). +Pigeon with native interop (direct FFI and JNI) generates direct native bindings using +FFI or JNI for mobile platforms (Android and iOS). Use this option if your use case requires any of the following: * You need synchronous execution on your native platform. -* You perform high-frequency data streaming (for example, audio or camera frames). -* You call native code from background Dart isolates. +* You are doing high-frequency data streaming (for example, audio or camera frames). +* You are calling native code from background Dart isolates. ### Pigeon with platform channels -Pigeon with platform channels generates standard platform channels that support -all Flutter platforms (Android, iOS, macOS, Windows, Linux, and Web). +Pigeon with platform channels generates standard platform channels that support all +platforms (Android, iOS, macOS, Windows, Linux, and Web). Use this option if your use case requires any of the following: -* You target platforms that do not yet support direct native bindings - (Windows, Linux, or Web). +* You need desktop (Windows, Linux). * You want zero external toolchains. ## Developer flow The developer experience with Pigeon follows a structured cycle: -1. **Define the schema:** - Define the communication contract in a Dart file (typically outside of your - `lib/` directory, such as `pigeons/messages.dart`). - This file defines data structures and API interfaces using Pigeon annotations - like `@HostApi` and `@FlutterApi`. - -2. **Generate the code:** - Run the Pigeon generator tool, which parses your definition file and produces - the necessary code for both Dart and your target native platforms: +1. Define the schema. +2. Generate the code. +3. Implement the native interface. +4. Call the API from Flutter. - ```bash - dart run pigeon --input pigeons/messages.dart - ``` +To start, define the communication definition in a Dart file (typically outside of your +`lib/` directory, such as `pigeons/message.dart`). This file defines the data structures +and the API interfaces using Pigeon annotations like `@HostApi` and `@FlutterApi`. Next, +run the Pigeon generator tool, which parses your definition file and produces the +necessary code for both Dart and your target native platform. - :::note - If you use Pigeon with native interop, set - `swiftOptions: SwiftOptions(useFfi: true)` and - `kotlinOptions: KotlinOptions(useJni: true)` in the `@ConfigurePigeon` - section of your schema. - For complete prerequisites and build configuration, - refer to the [Pigeon native interop guide][pigeon-native-guide]. - ::: - -3. **Implement the native interface:** - Implement the generated native interfaces in your host project (Swift, - Kotlin, Java, or C++) and register them with the Flutter engine. +:::note +If you’re using the Pigeon with native interop communication option, +set `swiftOptions: SwiftOptions(useFfi: true)` and `kotlinOptions: KotlinOptions(useJni: true)` +in the `@ConfigurePigeon` section of your schema. For complete prerequisites and build +configuration, refer to the [native interop guide][pigeon-native-guide]. +::: -4. **Call the API from Flutter:** - Call the generated Dart classes from your Flutter application, - or invoke the generated native APIs to call back into your Dart code. +After the code is generated, you implement the generated native interfaces in your host +project and register them with the Flutter engine. Finally, you call the generated Dart +classes from your Flutter application, or invoke the generated native APIs to call back +into your Dart code. -If you need to modify the API, update the definition file, -regenerate the code, and then update your implementation. +If you need to modify the API, first modify the definition file, regenerate the code, +and then update your implementation accordingly. ## Examples -To explore implementations of this flow, including Dart definitions, -generated code, and native implementations, consult: +To explore a complete implementation of this flow, including the Dart definition, +generated code, and native implementations, check out the following examples: -* [Pigeon example app in the flutter/packages repository][pigeon-example] -* [Flutter native interop demos repository][native-interop-demos] +* (coming soon) Example of Pigeon with native interop +* [Example of Pigeon with platform channels][pigeon-example] ## Constraints and limitations ### High-frequency data -For specialized use cases requiring direct manipulation of raw native pointers -or manual C/C++ memory management, use -[FFIgen](/platform-integration/interop/ffigen) or +For specialized use cases requiring direct manipulation of raw native pointers or C/C++ +memory management, use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. ### Inheritance -Pigeon does not support complex inheritance across data models. -Flatten your data models where possible. +Pigeon does not support complex inheritance. Flatten your data models where possible. -### Error handling +### (Pigeon with platform channels only) Async errors -* **Platform channels:** For `@async` methods, native code must return errors - through the provided callback handler. -* **Native bindings (FFI/JNI):** Throw native errors directly using standard - language features (`throw` in Swift `async throws` methods, - or throw exceptions in Kotlin `suspend` functions). +For `@async` methods, native code must return errors through the provided callback. ## Resources +* [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-native-guide] * [Pigeon package on pub.dev][pigeon-pub] -* [Pigeon source code on GitHub][pigeon-source] -* [Pigeon native interop guide][pigeon-native-guide] +* [Pigeon source code][pigeon-source] * [Flutter native interop demos][native-interop-demos] [pigeon-pub]: https://pub.dev/packages/pigeon From df196f75bc1f2680fd8c6f9a22633db17d12a09c Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 20 Oct 2026 04:39:41 +0000 Subject: [PATCH 03/16] Update Pigeon docs for macOS, Web, and native interop examples --- .../platform-integration/interop/pigeon.md | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/platform-integration/interop/pigeon.md index c08078b77e1..fc7823634aa 100644 --- a/sites/docs/src/content/platform-integration/interop/pigeon.md +++ b/sites/docs/src/content/platform-integration/interop/pigeon.md @@ -19,26 +19,30 @@ For low-level integrations that require direct access to native memory or C/C++ use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. ::: -### Pigeon with native interop +### Pigeon with native interop (recommended for Android, iOS, and macOS) -Pigeon with native interop (direct FFI and JNI) generates direct native bindings using -FFI or JNI for mobile platforms (Android and iOS). +Pigeon with native interop (direct FFI and JNI) generates direct native bindings +using FFI on iOS and macOS, and JNI on Android. +This is the recommended default when targeting Android, iOS, or macOS. -Use this option if your use case requires any of the following: +Use this option if you're building for Android, iOS, or macOS, +especially when your use case requires any of the following: -* You need synchronous execution on your native platform. -* You are doing high-frequency data streaming (for example, audio or camera frames). -* You are calling native code from background Dart isolates. +* Synchronous execution on your native platform. +* High-frequency data streaming (for example, audio or camera frames). +* Calling native code directly from background Dart isolates. ### Pigeon with platform channels -Pigeon with platform channels generates standard platform channels that support all -platforms (Android, iOS, macOS, Windows, Linux, and Web). +Pigeon with platform channels generates standard platform channels that support +Android, iOS, macOS, Windows, and Linux. +You can generate platform channels for Windows and Linux from the same Pigeon +file that uses native interop on Android, iOS, and macOS. Use this option if your use case requires any of the following: -* You need desktop (Windows, Linux). -* You want zero external toolchains. +* Desktop support on Windows or Linux. +* Zero external toolchains. ## Developer flow @@ -56,10 +60,12 @@ run the Pigeon generator tool, which parses your definition file and produces th necessary code for both Dart and your target native platform. :::note -If you’re using the Pigeon with native interop communication option, -set `swiftOptions: SwiftOptions(useFfi: true)` and `kotlinOptions: KotlinOptions(useJni: true)` -in the `@ConfigurePigeon` section of your schema. For complete prerequisites and build -configuration, refer to the [native interop guide][pigeon-native-guide]. +To enable Pigeon with native interop, set `swiftOptions: SwiftOptions(useFfi: true)`, +`kotlinOptions: KotlinOptions(useJni: true)`, and `appDirectory` (for example, +`'./'` for an app or `'example/'` for a plugin) in the `@ConfigurePigeon` +annotation of your Pigeon file. +For complete prerequisites and build configuration, +refer to the [Pigeon native interop guide][pigeon-native-guide]. ::: After the code is generated, you implement the generated native interfaces in your host @@ -73,10 +79,11 @@ and then update your implementation accordingly. ## Examples To explore a complete implementation of this flow, including the Dart definition, -generated code, and native implementations, check out the following examples: +generated code, and native implementations, refer to the following examples: -* (coming soon) Example of Pigeon with native interop +* [Example of Pigeon with native interop][pigeon-native-example] * [Example of Pigeon with platform channels][pigeon-example] +* [`shared_preferences` plugin][shared-preferences-source] (a production plugin using Pigeon with native interop) ## Constraints and limitations @@ -96,7 +103,8 @@ For `@async` methods, native code must return errors through the provided callba ## Resources -* [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-native-guide] +* [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-migration-guide] +* [Pigeon native interop guide][pigeon-native-guide] * [Pigeon package on pub.dev][pigeon-pub] * [Pigeon source code][pigeon-source] * [Flutter native interop demos][native-interop-demos] @@ -104,5 +112,8 @@ For `@async` methods, native code must return errors through the provided callba [pigeon-pub]: https://pub.dev/packages/pigeon [pigeon-source]: https://github.com/flutter/packages/tree/main/packages/pigeon [pigeon-example]: https://github.com/flutter/packages/tree/main/packages/pigeon/example +[pigeon-native-example]: https://github.com/flutter/packages/tree/main/packages/pigeon/example/native_interop_app [pigeon-native-guide]: https://github.com/flutter/packages/blob/main/packages/pigeon/native_interop_guide.md +[pigeon-migration-guide]: https://github.com/flutter/packages/blob/main/packages/pigeon/native_interop_migration_guide.md +[shared-preferences-source]: https://github.com/flutter/packages/tree/main/packages/shared_preferences [native-interop-demos]: https://github.com/flutter/demos/tree/main/native_interop_demos From ad3a14d4389d453b86512d90b5fec2aba408d720 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 20 Oct 2026 04:49:57 +0000 Subject: [PATCH 04/16] Address EAP feedback, naming, Mermaid overlap, and style polish across interop docs --- .../interop/bind-native-code.md | 8 +- .../platform-integration/interop/dart-ffi.md | 92 ++++--- .../platform-integration/interop/ffigen.md | 156 +++++++----- .../platform-integration/interop/index.md | 240 ++++++++++-------- .../platform-integration/interop/jnigen.md | 140 +++++----- .../platform-integration/interop/pigeon.md | 170 ++++++++----- .../platform-integration/platform-channels.md | 18 +- 7 files changed, 476 insertions(+), 348 deletions(-) diff --git a/sites/docs/src/content/platform-integration/interop/bind-native-code.md b/sites/docs/src/content/platform-integration/interop/bind-native-code.md index 1e33c8911e0..a5cec27b39a 100644 --- a/sites/docs/src/content/platform-integration/interop/bind-native-code.md +++ b/sites/docs/src/content/platform-integration/interop/bind-native-code.md @@ -23,7 +23,7 @@ Looking for the previous FFI plugin workflow? Consult which documents the `plugin_ffi` template and OS-specific build files. ::: -[build hooks]: https://dart.dev/tools/hooks +[build hooks]: {{site.dart-site}}/tools/hooks [dart:ffi]: {{site.dart.api}}/dart-ffi/dart-ffi-library.html [FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface @@ -116,7 +116,7 @@ DLL. :::note For desktop-specific guidance on calling system APIs—including wrapper packages like [`package:win32`]({{site.pub-pkg}}/win32) and -[Canonical's Linux packages]({{site.pub}}/publishers/canonical.com/packages)—see +[Canonical's Linux packages]({{site.pub}}/publishers/canonical.com/packages)—refer to [Integrating with Windows](/platform-integration/windows/building#integrating-with-windows) and [Integrate with Linux](/platform-integration/linux/building#integrate-with-linux). ::: @@ -215,8 +215,8 @@ void main(List args) async { You would need to handle different architectures and platforms by having different versions of your pre-compiled library. -For more examples, see the [code_assets package -examples](https://pub.dev/packages/code_assets/example). +To explore additional examples, consult the +[`package:code_assets` examples]({{site.pub-pkg}}/code_assets/example). ## Dynamic library naming guidelines diff --git a/sites/docs/src/content/platform-integration/interop/dart-ffi.md b/sites/docs/src/content/platform-integration/interop/dart-ffi.md index d9ef0bf7b75..fdcb0164123 100644 --- a/sites/docs/src/content/platform-integration/interop/dart-ffi.md +++ b/sites/docs/src/content/platform-integration/interop/dart-ffi.md @@ -1,56 +1,66 @@ --- title: Interact with native memory using dart:ffi shortTitle: dart:ffi -description: Learn how to interact with native memory using the dart:ffi library. +description: >- + Learn how to interact with native memory using the dart:ffi library. --- -The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library that -provides the low-level APIs to call C-ABI functions from Dart. - -Rather than a code generation tool like Pigeon, FFIgen, or JNIgen, `dart:ffi` is the -underlying technology that powers these tools and enables native memory interop. - -## When do you need dart:ffi - -When you use FFIgen or JNIgen to generate Dart bindings, the generated code does not -automatically convert Dart objects like Strings or Lists into native formats. -Because Dart memory is automatically garbage collected and native C memory is manually -managed, you must use `dart:ffi` to bridge this gap. - -The two most common scenarios when `dart:ffi` is needed are memory allocation and data conversion. - -With memory allocation, you use `dart:ffi` to allocate space on the system heap for -native arguments, and you must manually free that memory when done. This helps avoid -memory leaks. - -With data conversion, you use `dart:ffi` to convert Dart data types so they can be read -by native code. - -## Core tools: dart:ffi and package:ffi - -To write interop code, you use two libraries: - -* **`dart:ffi`:** This is built into the core Dart SDK. It provides native representation - types and lookup signatures. -* **`package:ffi`:** This package is a required dependency for working with `dart:ffi`. - It provides memory allocation methods and conversion helpers. +The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library +that provides low-level APIs to call C-ABI functions and manage native memory +from Dart. + +Unlike code generation tools such as [Pigeon](/platform-integration/interop/pigeon), +[FFIgen](/platform-integration/interop/ffigen), +or [JNIgen](/platform-integration/interop/jnigen), +`dart:ffi` is the underlying runtime layer that powers those tools +and enables direct native memory interop. + +## When to use `dart:ffi` + +When you use FFIgen or JNIgen to generate Dart bindings for low-level APIs, +the generated bindings often take native pointers or C structs +rather than Dart objects like `String` or `List`. +Because Dart memory is garbage collected +and native C memory is manually managed, +you use `dart:ffi` and `package:ffi` to bridge the two memory models: + +- **Memory allocation.** + Allocate space on the native heap for C pointers, structs, and buffers, + and free that memory when the native call finishes to prevent memory leaks. +- **Data conversion.** + Convert Dart types (such as `String` or typed data lists) into C-compatible + representations (such as `Pointer`) that native code can read. + +## Core libraries: `dart:ffi` and `package:ffi` + +To work with native memory in Dart, you use two libraries together: + +- **`dart:ffi`.** + Built into the core Dart SDK. + Provides native representation types (`Pointer`, `Struct`, `Union`, `Int32`), + `@Native` annotations, and dynamic library lookup APIs. +- **`package:ffi`.** + A companion package on pub.dev that provides native memory allocators + (`calloc`, `malloc`, and `Arena`) and string conversion helpers + (`toNativeUtf8`, `toDartString`). ## Concurrency in Flutter -FFI calls are synchronous and run on the thread they are called on. If you invoke a heavy -or blocking C function on your Flutter app's main UI thread, your app will freeze or drop -frames. To prevent this, you should execute blocking FFI calls inside a background Dart -isolate using `Isolate.run`. +FFI calls execute synchronously on the calling thread. +If you invoke a long-running or blocking C function on your Flutter app's +main UI isolate, your app drops frames or freezes. +To keep the UI responsive, run blocking FFI calls inside a background +Dart isolate using `Isolate.run`. ## Resources -To explore examples of memory allocation, string conversion, and dynamic library loading, -refer to these Dart interop resources: +To explore code examples for memory allocation, string conversion, +structs, and dynamic library loading, consult the following resources: -* [C interop guide (includes code examples for pointers, memory allocation, and structs)][dart-c-interop] -* [dart:ffi API reference][dart-ffi-api] -* [package:ffi on pub.dev][package-ffi-pub] +- [C interop guide on dart.dev][dart-c-interop] +- [`dart:ffi` API reference][dart-ffi-api] +- [`package:ffi` on pub.dev][package-ffi-pub] [dart-c-interop]: {{site.dart-site}}/interop/c-interop [dart-ffi-api]: {{site.dart.api}}/dart-ffi/dart-ffi-library.html -[package-ffi-pub]: https://pub.dev/packages/ffi +[package-ffi-pub]: {{site.pub-pkg}}/ffi diff --git a/sites/docs/src/content/platform-integration/interop/ffigen.md b/sites/docs/src/content/platform-integration/interop/ffigen.md index 581b91761ca..e29d518442b 100644 --- a/sites/docs/src/content/platform-integration/interop/ffigen.md +++ b/sites/docs/src/content/platform-integration/interop/ffigen.md @@ -1,117 +1,155 @@ --- title: FFIgen -description: Learn how to use FFIgen to generate type-safe Dart bindings for C, Objective-C, and Swift APIs. +description: >- + Learn how to use FFIgen to generate type-safe Dart bindings + for C, Objective-C, and Swift APIs. --- ## Overview -FFIgen (the Foreign Function Interface generator) is a code generator tool that creates -type-safe Dart bindings for C libraries and Apple frameworks (Objective-C and Swift). -This allows you to call Apple platform APIs (iOS and macOS) directly and synchronously +FFIgen (Foreign Function Interface generator) is a code generator tool +that creates type-safe Dart bindings for C libraries and Apple frameworks +(Objective-C and `@objc`-annotated Swift). +With FFIgen, you can call C, iOS, and macOS APIs directly and synchronously from your Dart code. :::note -Swift interop is currently in experimental support. +Direct Swift interop without Objective-C compatibility annotations (`@objc`) +is experimental and uses [`package:swiftgen`][swiftgen-pub]. +For production Swift plugins on iOS and macOS, +use [Pigeon](/platform-integration/interop/pigeon). ::: ## When to use FFIgen -Use FFIgen when you need to call C libraries, wrap compiled binaries (like C, C++, -Rust, or Go), or interact directly with Objective-C and Swift frameworks on iOS and macOS. +Use FFIgen when you need to call C libraries, +wrap compiled C-ABI binaries (from C, C++, Rust, or Go), +or interact directly with Objective-C frameworks on iOS and macOS. :::note -FFIgen parses C and Objective-C headers. To wrap C++ libraries, you must expose a -C-compatible interface around your C++ code. FFIgen cannot parse C++ classes or -templates directly. +FFIgen parses C and Objective-C headers. +To wrap a C++ library, expose a C-compatible interface (`extern "C"`) +around your C++ code. +FFIgen can't parse C++ classes or templates directly. ::: -For standard, cross-platform mobile plugin development where you don't have -critical data transfers, consider using [Pigeon](/platform-integration/interop/pigeon) instead. +For cross-platform plugin development on Android, iOS, and macOS +where you don't need direct access to raw C pointers, +use [Pigeon](/platform-integration/interop/pigeon) instead. ## Developer flow -Generating bindings with FFIgen follows these steps: +Generating bindings with FFIgen follows four steps: -1. Reference the native headers. -2. Write the FFIgen configuration file. -3. Generate the bindings by running FFIgen. -4. Call the generated code from Dart. +1. Reference the native headers. +1. Write the FFIgen configuration script. +1. Generate the bindings by running the configuration script. +1. Call the generated code from Dart. -To start, identify the native header files or APIs you want to wrap. Then write a -configuration file (typically `ffigen.yaml` or `tool/ffigen.dart`). This configuration specifies -where the header files are located and where to output the generated Dart code. +To start, identify the native header files or Apple framework APIs +that you want to wrap. +Then create a configuration script in `tool/ffigen.dart` +(some existing templates, such as `flutter create --template=package_ffi`, +use a `ffigen.yaml` file). +This configuration specifies where the header files are located +and where to write the generated Dart bindings. -Next, run the generator tool (such as `dart run ffigen`), which parses the headers and -generates the corresponding Dart bindings. +Next, run the configuration script (`dart run tool/ffigen.dart`, +or `dart run ffigen` if your project uses `ffigen.yaml`), +which parses the headers and generates the corresponding Dart bindings. -Last, import the generated Dart file into your app and call the functions directly. -Because FFIgen communicates directly using the underlying FFI layer, refer to the -[dart:ffi guide](/platform-integration/interop/dart-ffi) to learn how to manage native -memory lifetimes and execute native calls without blocking the UI thread. +Finally, import the generated Dart file into your app or package +and call the functions directly. +Because FFIgen communicates directly through the underlying FFI layer, +consult the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +to learn how to manage native memory lifetimes +and run blocking native calls without freezing the UI thread. :::note -If the native API changes, you need to update your configuration script and regenerate the bindings. +When the native API changes, update your configuration script +and regenerate the bindings. ::: -## Example +## Examples -To explore a complete implementation of this flow, including how to configure the -generator, check out the [official FFIgen examples][ffigen-examples]. +To explore complete implementations of this workflow, +including how to configure the generator, +refer to the [official FFIgen examples][ffigen-examples]: -The Add example highlights the general flow with a toy native API (one function in a single header file). - -The `code_assets` examples highlight real-world examples like system hostname lookups and audio playback. +- The `simple` example demonstrates the general workflow with a minimal C API + (a single function in one header file). +- The `objective_c` and `swift` examples demonstrate calling Apple frameworks + using `tool/ffigen.dart`. +- The [`code_assets` examples][code-assets-examples] demonstrate real-world + bundling use cases, such as system hostname lookups and audio playback. ## Constraints and limitations -Some general considerations when working with FFIgen: +Keep the following general considerations in mind when working with FFIgen: ### Source header files -FFIgen uses `libclang` to parse headers. Always resolve compiler warnings logged during -generation to prevent runtime crashes. - -### OS differences +FFIgen uses `libclang` to parse headers. +Resolve any compiler warnings logged during generation +to prevent unexpected runtime crashes. -Dart has no equivalent of C macros (like `#if TARGET_OS_IPHONE`), so you must handle minor -API variations using runtime `Platform.isIOS` checks. +### Operating system differences -Some Objective-C and Swift-specific pitfalls when working with FFIgen: +Dart has no preprocessor equivalent to C macros +(such as `#if TARGET_OS_IPHONE`), +so handle platform-specific API variations at runtime +using `Platform.isIOS` or `Platform.isMacOS` checks. ### Objective-C bindings -Objective-C methods are dynamically resolved. FFIgen automatically maps method selectors -to Dart signatures, but you should verify selectors by reading the bindings if you are -using heavily overloaded native methods. +Objective-C methods are resolved dynamically at runtime. +FFIgen maps Objective-C selectors to Dart method signatures automatically, +but inspect the generated bindings if you wrap heavily overloaded +native methods. ### Objective-C memory management -Dart uses garbage collection while Objective-C uses reference counting. Passing Dart closures -that close over native wrapper objects can easily create reference cycles and leak memory. +Dart uses garbage collection, whereas Objective-C uses reference counting. +Passing Dart closures that capture native wrapper objects can create +reference cycles across runtimes and leak memory. +To avoid retain cycles, consult the +[Objective-C memory management guide][objc-gc-doc]. -### Objective-C runtime checks +### Objective-C runtime type checks -FFIgen wrappers enforce Dart static types, but the underlying Objective-C object might have -a different runtime subclass, so Dart 'is' and 'as' keywords won't work as expected. +FFIgen wrappers enforce static Dart types, +but the underlying Objective-C object might be a different subclass at runtime. +Because of this, Dart's `is` and `as` keywords don't check the Objective-C +runtime type. Use `.isA()` and `.as()` instead. ### Objective-C multithreading -UI-related Objective-C calls must run on the iOS main platform thread. Also, some types -of callbacks can crash your app if they are executed on the wrong thread. +UI-related Objective-C calls must run on the platform's main thread. +Some native callbacks can also crash your app if invoked on the wrong thread. ### Swift compatibility -Consider using the experimental Swiftgen package for this use case. FFIgen interacts with -Swift through Objective-C. Pure Swift features (such as structs, generics, or enums -associated with values) are not supported. Classes must be annotated with `@objc`. +FFIgen interacts with Swift through Objective-C compatibility headers. +Swift classes and methods must be annotated with `@objc` (or `@objcMembers`) +and inherit from `NSObject`. +Pure Swift features without an Objective-C representation—such as Swift structs, +generics, and enums with associated values—aren't supported by FFIgen. +For Swift plugins, use [Pigeon](/platform-integration/interop/pigeon), +or explore the experimental [`package:swiftgen`][swiftgen-pub] package. ## Resources -* [FFIgen package on pub.dev][ffigen-pub] -* [objective_c package on pub.dev][objc-pub] -* [Official FFIgen examples on GitHub][ffigen-examples] +To learn about generating C and Objective-C bindings, +consult the following resources: + +- [`package:ffigen` on pub.dev][ffigen-pub] +- [`package:objective_c` on pub.dev][objc-pub] +- [Official FFIgen examples on GitHub][ffigen-examples] -[ffigen-pub]: https://pub.dev/packages/ffigen -[objc-pub]: https://pub.dev/packages/objective_c +[ffigen-pub]: {{site.pub-pkg}}/ffigen +[objc-pub]: {{site.pub-pkg}}/objective_c +[swiftgen-pub]: {{site.pub-pkg}}/swiftgen [ffigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/ffigen/example +[code-assets-examples]: {{site.pub-pkg}}/code_assets/example +[objc-gc-doc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_gc.md diff --git a/sites/docs/src/content/platform-integration/interop/index.md b/sites/docs/src/content/platform-integration/interop/index.md index 040beea763a..16e208d8f2a 100644 --- a/sites/docs/src/content/platform-integration/interop/index.md +++ b/sites/docs/src/content/platform-integration/interop/index.md @@ -4,168 +4,192 @@ description: Learn about the options to call platform-specific code in your app. --- Flutter apps run on multiple platforms, -but sometimes you need to communicate directly -with the host operating system or run code from an existing native library. +but sometimes you need to communicate directly with the host operating system +or call code from an existing native library. -To solve this, Flutter provides several interop tools ranging from -code generators to direct FFI bindings. +To support these use cases, Flutter and Dart provide interop tools +that range from high-level, type-safe code generators to low-level FFI bindings. Use the following decision tree to choose your interop solution, -or learn more about the native interop layers. +or read on to learn how to package and structure native interop code. ## Choose your interop solution ```mermaid flowchart TD - Start["What is your primary goal?"] --> GoalA["Platform integration
    (Swift, Kotlin, Java, Obj-C)"] - Start --> GoalB["Wrap native C-ABI library
    (C, Rust, Go)"] - Start --> GoalC["Standalone Dart package
    (No Flutter framework)"] + Start(["Step 1: What is your primary goal?"]) --> GoalA["Goal A: Platform integration
    (Swift, Kotlin, Java, Obj-C)"] + Start --> GoalB["Goal B: C-ABI wrapping
    (C, C++, Rust, Go)"] + Start --> GoalC["Goal C: Pure Dart package
    (No Flutter framework)"] - GoalB --> FFIgen["Use FFIgen
    (or Flutter Rust Bridge)"] - GoalC --> Direct["Use FFIgen / JNIgen"] + GoalB --> FFIgen["Use FFIgen
    (or Flutter Rust Bridge)"]:::directNode + GoalC --> Direct["Use FFIgen or JNIgen"]:::directNode - GoalA --> EvalPigeon{"Building a Flutter plugin?
    Evaluate constraints"} - EvalPigeon -->|"Standard API surface
    (Default / Recommended)"| Pigeon["Use Pigeon"] - EvalPigeon -->|"High-frequency data streaming,
    complex cross-language subclasses,
    or manual memory management"| DirectInterop["Use FFIgen / JNIgen"] + GoalA --> EvalPigeon{"Step 2: Do high-complexity
    constraints apply?"} + EvalPigeon -->|"No (most common)"| Pigeon["Use Pigeon
    (Recommended default)"]:::recommendedNode + EvalPigeon -->|"Yes (advanced)"| DirectInterop["Use FFIgen or JNIgen"]:::directNode + + classDef recommendedNode stroke:#1e8e3e,stroke-width:2px; + classDef directNode stroke:#1a73e8,stroke-width:2px; ``` ### Step 1: Determine your primary goal -* **Goal A: Platform integration** - Communicate with native OS features (Swift, Kotlin, Java, Objective-C) - or expose an API surface. - ➡ (Proceed to Step 2) -* **Goal B: C-ABI wrapping** - Wrap an existing native library (C, Rust, Go). - 👉 Use [FFIgen](/platform-integration/interop/ffigen) - (or Flutter Rust Bridge for Rust). -* **Goal C: Pure Dart package** - Build a Dart CLI tool without Flutter framework dependencies. - 👉 Use [FFIgen](/platform-integration/interop/ffigen) or +Identify which of the following goals matches your project: + +- **Goal A: Platform integration.** + Communicate with native operating system APIs + (Swift, Kotlin, Java, or Objective-C) or expose a plugin API surface. + Proceed to [Step 2: Evaluate Pigeon for Flutter plugins](#step-2-evaluate-pigeon-for-flutter-plugins). +- **Goal B: C-ABI wrapping.** + Wrap an existing native library written in C, C++ (with `extern "C"`), + Rust, or Go. + **Recommendation:** Use [FFIgen](/platform-integration/interop/ffigen) + (or [Flutter Rust Bridge][frb] for Rust). +- **Goal C: Pure Dart package.** + Build a standalone Dart CLI tool or backend package without Flutter framework + dependencies. + **Recommendation:** Use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen). ### Step 2: Evaluate Pigeon for Flutter plugins -If you are building a Flutter plugin, -first verify whether Pigeon satisfies your technical constraints. +If you're building a Flutter plugin or calling host platform APIs, +verify whether [Pigeon](/platform-integration/interop/pigeon) fits your +technical constraints before choosing low-level direct bindings. :::important -Migrating a plugin between Pigeon and FFIgen/JNIgen is non-trivial. -Choose your interop solution carefully based on your initial project constraints. -With Pigeon, you define APIs in Dart, generate native interfaces, and write -glue code in native languages (Kotlin, Swift, C++). -With FFIgen/JNIgen, you generate Dart APIs from native code and write glue code -in Dart. -Because glue code lives in completely different languages and architectural -layers, switching approaches requires rewriting the integration. +Migrating a plugin between Pigeon and FFIgen or JNIgen requires rewriting your +integration. +With Pigeon, you define APIs in Dart, generate native interfaces, +and write glue code in native languages (Kotlin, Swift, or C++). +With FFIgen and JNIgen, you generate Dart bindings from native APIs +and write glue code in Dart. +Because the glue code lives in different languages and architectural layers, +choose your interop tool carefully based on your project's constraints. ::: -Do any of the following requirements apply to your feature? +Check whether any of the following advanced requirements apply to your feature: -* Your app processes high-frequency raw data (e.g., video processing). -* Your integration requires advanced language bridging - (e.g., cross-language subclasses, delegates, or proxies). -* Your feature requires manual memory management and multithreading over +- Passing large volumes of raw data using native pointers + (for example, custom video frame buffers). +- Advanced cross-language object bridging + (such as cross-language subclasses, delegates, or proxies). +- Manual memory management and custom multithreading instead of automated guardrails. -* Your target language is C, C++, Rust, or Go. +- Calling C, C++, Rust, or Go libraries directly. -**NO (Most common)** 👉 **USE [PIGEON](/platform-integration/interop/pigeon).** -This is the default, recommended path. -It provides necessary guardrails, creates easily digestible interface mappings, -and is highly efficient for most API surface areas. +Based on those requirements, choose one of the following paths: -**YES** 👉 **USE [FFIgen](/platform-integration/interop/ffigen) / -[JNIgen](/platform-integration/interop/jnigen).** -You need the flexibility, complexity, and performance of direct interop for -larger surface areas. +- **No (most common): Use [Pigeon](/platform-integration/interop/pigeon).** + Pigeon is the recommended default path for Flutter plugins. + It provides type-safe contracts, handles memory lifetimes and data conversion, + and calls Swift and Kotlin directly through FFI and JNI on Android, iOS, + and macOS. +- **Yes: Use [FFIgen](/platform-integration/interop/ffigen) or + [JNIgen](/platform-integration/interop/jnigen).** + Direct interop generators give you full control over large native API surfaces + and raw memory. :::note -Directly using FFIgen and JNIgen introduces potential pitfalls, -particularly around threading and memory management. -Pigeon is generally recommended because it has fewer potential pitfalls -and provides safer guardrails. +Using FFIgen and JNIgen directly requires managing +[threading][jnigen-threading] and [native memory lifetimes][objc-gc] yourself. +Pigeon is recommended for most plugins because it handles these concerns +automatically. ::: -## Packaging your interop code - -Depending on how you plan to share your code, -choose the option that best suits your use case. +## Package your interop code -:::note -To learn about the difference between a package and a plugin, +Depending on how you plan to organize and share your code, +choose the packaging option that fits your use case. +To learn about the difference between a Dart package and a Flutter plugin, refer to [Using packages](/packages-and-plugins/using-packages). -::: -* **In-app integration** - Write your native interop code directly inside your Flutter app without - creating a separate package. -* **Plugin** - A specialized package that makes native platform functionality (such as a - device's camera or sensors) available to your app +- **[In-app integration](/platform-integration/platform-channels).** + Write native interop code directly inside your Flutter app without creating + a separate package. +- **[Plugin package](/packages-and-plugins/developing-packages#plugin).** + Create a specialized package that exposes platform-specific functionality + (such as camera or sensor APIs) to Flutter apps (`flutter create --template=plugin`). - To learn more, refer to - [Developing plugin packages](/packages-and-plugins/developing-packages#plugin). -* **FFI package** - A package that compiles and bundles native C/C++ code using Dart build hooks - and `dart:ffi`. - This method also includes an `ffigen.yaml` configuration file to regenerate - Dart bindings (`flutter create --template=package_ffi`). - To learn more, refer to - [Developing FFI packages](/packages-and-plugins/developing-packages#ffi). +- **[FFI package](/packages-and-plugins/developing-packages#ffi).** + Create a package that compiles and bundles native C or C++ code using + [Dart build hooks][dart-hooks] and [`dart:ffi`](/platform-integration/interop/dart-ffi), + and includes an [FFIgen](/platform-integration/interop/ffigen) configuration + to regenerate Dart bindings (`flutter create --template=package_ffi`). :::note -If you're using pre-compiled binaries (such as `.so` files on Android or -`.dylib` files on iOS), use `CodeAsset` in your `hook/build.dart` script to -bundle them into your package. +If you're bundling pre-compiled binaries (such as `.so` files on Android +or `.dylib` files on iOS and macOS), use `CodeAsset` in your `hook/build.dart` +script to bundle them into your package. +For details, refer to +[Bind to native code using FFI](/platform-integration/interop/bind-native-code#closed-source-libraries). ::: -For more details on package authoring, -refer to [Developing packages & plugins](/packages-and-plugins/developing-packages). +To learn about authoring packages, +consult [Developing packages and plugins](/packages-and-plugins/developing-packages). ## Tool comparison +The following table compares direct interop generators (FFIgen and JNIgen) +and Pigeon across target languages: + | Target language | Direct interop | Pigeon | -|---|---|---| -| **C** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended. Supported with platform channels, but not maintained by the Flutter team. | -| **C++ with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | -| **C++ without `extern "C"`** | FFIgen (not yet supported) | Supported with platform channels, but not maintained by the Flutter team. | -| **Rust with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | -| **Rust without `extern "C"`** | Flutter Rust Bridge | Not supported | -| **Go with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| --- | --- | --- | +| **C** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (supported with platform channels, but not maintained by the Flutter team) | +| **C++ with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (refer to C) | +| **C++ without `extern "C"`** | Not supported | Supported on Windows with platform channels | +| **Rust with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (refer to C) | +| **Rust without `extern "C"`** | [Flutter Rust Bridge][frb] | Not supported | +| **Go with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (refer to C) | | **Go without `extern "C"`** | Not supported | Not supported | -| **Java** | [JNIgen](/platform-integration/interop/jnigen) | Supported via Kotlin | -| **Kotlin** | [JNIgen](/platform-integration/interop/jnigen) | Supported | -| **Objective-C** | [FFIgen](/platform-integration/interop/ffigen) | Supported via Swift | -| **Swift without Obj-C compatibility annotations** | Swiftgen (experimental) | Supported | +| **Java** | [JNIgen](/platform-integration/interop/jnigen) | Supported through Kotlin | +| **Kotlin** | [JNIgen](/platform-integration/interop/jnigen) | Supported (recommended) | +| **Objective-C** | [FFIgen](/platform-integration/interop/ffigen) | Supported through Swift | +| **Swift with `@objc` annotations** | [FFIgen](/platform-integration/interop/ffigen) | Supported (recommended) | +| **Swift without `@objc` annotations** | [`package:swiftgen`][swiftgen-pub] (experimental) | Supported (recommended) | -* Direct interop: Lower-level direct bindings, higher performance for large data, - requires manual memory and threading management. -* Pigeon: Structured type-safe code generator, recommended default for plugins. +{:.table .table-striped} -## Understanding interop layers +- **Direct interop:** Generates low-level bindings directly against native + headers or classes; requires manual memory and threading management. +- **Pigeon:** Generates structured, type-safe API contracts between Dart and the + host platform; recommended default for Flutter plugins. -Dart structures native interop into three distinct layers: -low-level bindings, idiomatic Dart APIs, and framework-specific plugins. +## Understand interop layers +Dart structures native interop into three architectural layers: +low-level bindings, idiomatic Dart APIs, and framework-specific plugins. For complete architectural details, -consult the [native interop layers guide](https://github.com/dart-lang/native/blob/main/doc/interop-layers.md). +consult the [native interop layers guide][interop-layers]. ### Layer 1: Low-level bindings -Layer 1 consists of thin, high-performance, and unopinionated wrappers around -native code (for example, `dart:ffi`, `dart:js_interop`, FFIgen, and JNIgen). -These APIs mirror native signatures closely but require manual memory and -threading management. +Layer 1 consists of thin, unopinionated wrappers around native APIs +(such as [`dart:ffi`](/platform-integration/interop/dart-ffi), +`dart:js_interop`, [FFIgen](/platform-integration/interop/ffigen), +and [JNIgen](/platform-integration/interop/jnigen)). +These bindings mirror native signatures closely, +but require you to manage memory and threading manually. ### Layer 2: Idiomatic Dart APIs -Layer 2 consists of idiomatic Dart APIs that wrap the low-level layer to hide -platform-specific complexities (for example, Pigeon or the `sqlite3` package). -These manage concurrency, memory lifetimes, and data conversions automatically. +Layer 2 wraps the low-level bindings in idiomatic Dart APIs to hide +platform-specific details (for example, [Pigeon](/platform-integration/interop/pigeon) +or [`package:sqlite3`][sqlite3-pub]). +This layer manages concurrency, memory lifetimes, and data conversions +automatically. ### Layer 3: Framework-specific plugins -Layer 3 provides integration with framework-specific APIs (for example, the -Flutter Plugin API). -This layer exposes Dart APIs to the framework while hiding platform details -like Android's `Context` or iOS's application lifecycle. +Layer 3 integrates with framework-specific lifecycle and UI APIs +(such as the Flutter Plugin API). +This layer exposes clean Dart APIs to your Flutter app while encapsulating +platform objects like Android's `Context` or iOS's application lifecycle. + +[frb]: {{site.pub-pkg}}/flutter_rust_bridge +[swiftgen-pub]: {{site.pub-pkg}}/swiftgen +[sqlite3-pub]: {{site.pub-pkg}}/sqlite3 +[dart-hooks]: {{site.dart-site}}/tools/hooks +[jnigen-threading]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/threading.md +[objc-gc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_gc.md +[interop-layers]: https://github.com/dart-lang/native/blob/main/doc/interop-layers.md diff --git a/sites/docs/src/content/platform-integration/interop/jnigen.md b/sites/docs/src/content/platform-integration/interop/jnigen.md index 44c09a14619..4e8bb270a08 100644 --- a/sites/docs/src/content/platform-integration/interop/jnigen.md +++ b/sites/docs/src/content/platform-integration/interop/jnigen.md @@ -1,103 +1,127 @@ --- title: JNIgen -description: Learn how to use JNIgen to generate type-safe Dart bindings for Java and Kotlin APIs. +description: >- + Learn how to use JNIgen to generate type-safe Dart bindings + for Java and Kotlin APIs. --- ## Overview -JNIgen (Java Native Interface generator) is a code generator tool that creates -type-safe Dart bindings for Java and Kotlin libraries and APIs. -This allows you to call Android APIs directly and synchronously from your Dart code. +JNIgen (Java Native Interface generator) is a code generator tool +that creates type-safe Dart bindings for Java and Kotlin libraries and APIs. +With JNIgen, you can call Android APIs, JARs, and AARs directly +and synchronously from your Dart code. ## When to use JNIgen -Use JNIgen when you need to call Java or Kotlin libraries, wrap compiled JAR -or AAR files, or interact directly with Android from your Dart code. +Use JNIgen when you need to call Java or Kotlin libraries directly from Dart, +wrap compiled JAR or AAR files, +or build a standalone Dart package that interacts with the JVM. -Because JNIgen uses direct JNI bindings through `dart:ffi`, -it is also the recommended tool when you require high-performance, -low-latency interop, or need to pass massive amounts of raw data between Dart -and the JVM. +Because JNIgen calls into the JVM directly through `dart:ffi`, +it's also suited for high-performance, low-latency interop +or passing large volumes of raw data between Dart and the JVM. -For standard, cross-platform mobile plugin development where you don't have -critical data transfers, consider using [Pigeon](/platform-integration/interop/pigeon) instead. +For standard Android plugin development where you don't need direct access +to raw JNI references, use [Pigeon](/platform-integration/interop/pigeon) +with native interop (`useJni: true`) instead. ## Developer flow -Generating bindings with JNIgen follows this general flow: - -1. Reference the native classes or JARs. -2. Write the JNIgen configuration file. -3. Generate the bindings by running JNIgen. -4. Call the generated code from Dart. - -:::note -For a complete list of configuration options, consult the JNIgen documentation -on pub.dev and the JNIgen README. -::: - -To start, identify the native Java classes, packages, or compiled JAR/AAR files -you want to wrap. Then write a configuration file (typically `jnigen.yaml` or -`tool/jnigen.dart`). This configuration specifies where the native files are -located, what packages to include, and where to output the generated Dart code. - -Next, run the generator tool (such as `dart run jnigen --config jnigen.yaml`), +Generating bindings with JNIgen follows four steps: + +1. Reference the native classes, JARs, or AARs. +1. Write the JNIgen configuration script. +1. Generate the bindings by running the configuration script. +1. Call the generated code from Dart. + +To start, identify the Java or Kotlin classes, packages, +or compiled JAR and AAR files that you want to wrap. +Then create a configuration script in `tool/jnigen.dart` +(some older projects use a legacy `jnigen.yaml` file). +This script specifies where the native files are located, +which classes or packages to include, +and where to output the generated Dart bindings. +For a complete list of configuration options, +consult the [`package:jnigen` documentation on pub.dev][jnigen-pub]. + +Next, run the configuration script (`dart run tool/jnigen.dart`, +or `dart run jnigen --config jnigen.yaml` if your project uses `jnigen.yaml`), which parses the classes and generates the corresponding Dart bindings. -Last, import the generated Dart file into your app and call the functions directly. -Because JNIgen communicates directly using the underlying FFI layer, -refer to the [dart:ffi guide](/platform-integration/interop/dart-ffi) to learn -how to manage native memory lifetimes and execute native calls without blocking -the UI thread. +Finally, import the generated Dart file into your app or package +and call the methods directly. +Because JNIgen communicates directly through the underlying FFI layer, +consult the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +to learn how to manage native memory lifetimes +and run blocking native calls without freezing the UI thread. :::note -If the native API changes, you need to update your configuration script and -regenerate the bindings. +When the native API changes, update your configuration script +and regenerate the bindings. ::: ## Examples -To explore complete implementations of this flow, including how to configure the -generator, check out the [permissions_plugin demo][permissions-demo] and the -[android_toast_demo][toast-demo] in the official Flutter native interop demos repository. +To explore complete implementations of this workflow, +including how to configure the generator, +refer to the [`permissions_plugin` demo][permissions-demo] +and the [`android_toast_demo`][toast-demo] +in the Flutter native interop demos repository. ## Constraints and limitations -There are some general considerations and limitations when working with JNIgen. +Keep the following considerations and limitations in mind +when working with JNIgen: ### Threading considerations JNIgen calls execute synchronously on the calling thread. Watch out for two common scenarios: -* Calling platform APIs that must run on the platform thread. Use `runOnPlatformThread` from `dart:ui`. -* Dart callbacks from Java run on the isolate that created them. This can lead to deadlocks if a blocked Java thread waits for a blocked Dart thread. +- **Platform thread APIs.** + To call Android APIs that must run on the platform main thread, + use `runOnPlatformThread` from `dart:ui`. +- **Cross-thread callbacks.** + Dart callbacks invoked from Java or Kotlin run on the Dart isolate + that created them. + Avoid blocking a Java thread while waiting for a Dart isolate that is itself + blocked waiting on that Java thread, which causes a deadlock. ### Lifecycle of Java objects -JNI wrappers use Java global references. There is a hard limit of 50,000 active references. -You should call `.release()` or use `.using()` blocks to prevent memory leaking. -Use `WeakReference` to break reference cycles between Dart and Java GCs when implementing interfaces. +JNI wrappers hold Java global references, +which have an Android runtime limit of approximately 50,000 active references. +Call `.release()` or scope objects inside `.using()` blocks +to free references promptly and prevent memory leaks. +When implementing Java interfaces in Dart, use `WeakReference` +to avoid reference cycles between the Dart and JVM garbage collectors. ### JVM runtime type checks -JNI wrappers are Dart extension types, and they compile down to `JObject` at runtime. -Because of this, Dart's native 'is' and 'as' keywords do not check the Java runtime type. +JNI wrappers are Dart extension types that compile to `JObject` at runtime. +Because of this, Dart's `is` and `as` keywords don't check +the Java runtime type. Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. -### General debugging +### Debugging reference errors -Tracking down JNI memory allocation bugs like `DoubleReleaseError` and `UseAfterReleaseError` can be difficult. -Set `Jni.captureStackTraceOnRelease = true` to log stack traces and help trace leaks. +To trace JNI reference lifecycle bugs such as `DoubleReleaseError` +and `UseAfterReleaseError`, +set `Jni.captureStackTraceOnRelease = true` to capture allocation and release +stack traces. ## Resources -* [JNIgen package on pub.dev][jnigen-pub] -* [JNI package on pub.dev][jni-pub] -* [Official JNIgen examples on GitHub][jnigen-examples] +To learn about generating Java and Kotlin bindings, +consult the following resources: + +- [`package:jnigen` on pub.dev][jnigen-pub] +- [`package:jni` on pub.dev][jni-pub] +- [Official JNIgen examples on GitHub][jnigen-examples] -[jnigen-pub]: https://pub.dev/packages/jnigen -[jni-pub]: https://pub.dev/packages/jni +[jnigen-pub]: {{site.pub-pkg}}/jnigen +[jni-pub]: {{site.pub-pkg}}/jni [jnigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example -[permissions-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/permissions_plugin -[toast-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/android_toast_demo +[permissions-demo]: {{site.repo.demos}}/tree/main/native_interop_demos/permissions_plugin +[toast-demo]: {{site.repo.demos}}/tree/main/native_interop_demos/android_toast_demo diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/platform-integration/interop/pigeon.md index fc7823634aa..b26fd773ecf 100644 --- a/sites/docs/src/content/platform-integration/interop/pigeon.md +++ b/sites/docs/src/content/platform-integration/interop/pigeon.md @@ -1,119 +1,151 @@ --- title: Pigeon -description: Learn how to use Pigeon to generate type-safe bindings between your Dart code and the host platform. +description: >- + Learn how to use Pigeon to generate type-safe bindings + between your Dart code and the host platform. --- ## Overview Pigeon is the recommended default tool for building Flutter plugins. -Pigeon replaces the manual setup of raw platform channels with generated, -type-safe APIs. This helps prevent typos in method names or mismatched argument types. +Pigeon replaces the manual setup of raw platform channels +with generated, type-safe APIs, +which prevents typos in method names and mismatched argument types. -## Choosing a communication option with Pigeon +## Choose a communication option -Pigeon supports the following two communication options depending on your target -platform and performance needs: native interop (direct FFI and JNI) or platform channels. +Pigeon supports two communication options depending on your target platform +and performance requirements: +native interop (direct FFI and JNI) and platform channels. :::note -For low-level integrations that require direct access to native memory or C/C++ libraries, -use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. +For low-level integrations that require direct access to native memory +or C and C++ libraries, use [FFIgen](/platform-integration/interop/ffigen) +or [JNIgen](/platform-integration/interop/jnigen) instead. ::: ### Pigeon with native interop (recommended for Android, iOS, and macOS) -Pigeon with native interop (direct FFI and JNI) generates direct native bindings +Pigeon with native interop generates direct native bindings using FFI on iOS and macOS, and JNI on Android. This is the recommended default when targeting Android, iOS, or macOS. -Use this option if you're building for Android, iOS, or macOS, -especially when your use case requires any of the following: +Use this option when building for Android, iOS, or macOS, +especially if your use case requires any of the following capabilities: -* Synchronous execution on your native platform. -* High-frequency data streaming (for example, audio or camera frames). -* Calling native code directly from background Dart isolates. +- Synchronous execution on the native platform. +- High-frequency data streaming (for example, audio or camera frames). +- Calling native code directly from background [Dart isolates][]. ### Pigeon with platform channels Pigeon with platform channels generates standard platform channels that support Android, iOS, macOS, Windows, and Linux. -You can generate platform channels for Windows and Linux from the same Pigeon -file that uses native interop on Android, iOS, and macOS. +You can generate platform channels for Windows and Linux from the same +Pigeon definition file that uses native interop on Android, iOS, and macOS. -Use this option if your use case requires any of the following: +Use this option if your use case requires any of the following capabilities: -* Desktop support on Windows or Linux. -* Zero external toolchains. +- Desktop support on Windows or Linux. +- Zero external toolchains. ## Developer flow -The developer experience with Pigeon follows a structured cycle: +The developer workflow with Pigeon follows four steps: + +1. Create the Pigeon definition file. +1. Generate the bindings. +1. Implement the native interface. +1. Call the API from Flutter. + +To start, create a Pigeon definition file in Dart +(typically outside of your `lib/` directory, such as `pigeons/messages.dart`). +This file defines your data structures and API interfaces using Pigeon +annotations such as `@HostApi` and `@FlutterApi`. +Next, run the Pigeon generator tool (`dart run pigeon`), +which parses your Pigeon definition file and generates code for both Dart +and your target native platforms. + +To turn on native interop, set `appDirectory`, `swiftOptions`, +and `kotlinOptions` in the `@ConfigurePigeon` annotation at the top of your +Pigeon definition file: + +```dart title="pigeons/messages.dart" +@ConfigurePigeon( + PigeonOptions( + // Tells FFIgen and JNIgen where your compiled app lives + // (use './' for an app, or 'example/' for a plugin). + appDirectory: 'example/', + // iOS and macOS: call your Swift implementation directly through FFI. + swiftOptions: SwiftOptions(useFfi: true), + // Android: call your Kotlin implementation directly through JNI. + kotlinOptions: KotlinOptions(useJni: true), + ), +) +``` -1. Define the schema. -2. Generate the code. -3. Implement the native interface. -4. Call the API from Flutter. - -To start, define the communication definition in a Dart file (typically outside of your -`lib/` directory, such as `pigeons/message.dart`). This file defines the data structures -and the API interfaces using Pigeon annotations like `@HostApi` and `@FlutterApi`. Next, -run the Pigeon generator tool, which parses your definition file and produces the -necessary code for both Dart and your target native platform. - -:::note -To enable Pigeon with native interop, set `swiftOptions: SwiftOptions(useFfi: true)`, -`kotlinOptions: KotlinOptions(useJni: true)`, and `appDirectory` (for example, -`'./'` for an app or `'example/'` for a plugin) in the `@ConfigurePigeon` -annotation of your Pigeon file. For complete prerequisites and build configuration, -refer to the [Pigeon native interop guide][pigeon-native-guide]. -::: +consult the [Pigeon native interop guide][pigeon-native-guide]. -After the code is generated, you implement the generated native interfaces in your host -project and register them with the Flutter engine. Finally, you call the generated Dart -classes from your Flutter application, or invoke the generated native APIs to call back -into your Dart code. +After generating the code, implement the generated native interfaces +in your app's or plugin's native platform directory +(such as `android/`, `ios/`, or `macos/`) +and register them with the Flutter engine. +Finally, call the generated Dart classes from your Flutter app, +or invoke the generated native APIs to call back into your Dart code. -If you need to modify the API, first modify the definition file, regenerate the code, -and then update your implementation accordingly. +To modify the API later, update the Pigeon definition file, +regenerate the code, and update your native implementation. ## Examples -To explore a complete implementation of this flow, including the Dart definition, -generated code, and native implementations, refer to the following examples: +To explore complete implementations of this workflow, +including the Pigeon definition file, generated code, +and native implementations, refer to the following examples: -* [Example of Pigeon with native interop][pigeon-native-example] -* [Example of Pigeon with platform channels][pigeon-example] -* [`shared_preferences` plugin][shared-preferences-source] (a production plugin using Pigeon with native interop) +- [Example of Pigeon with native interop][pigeon-native-example] +- [Example of Pigeon with platform channels][pigeon-example] +- [`shared_preferences` plugin][shared-preferences-source] + (a production plugin that uses Pigeon with native interop) ## Constraints and limitations -### High-frequency data +Keep the following constraints in mind when working with Pigeon: + +### High-frequency raw pointers -For specialized use cases requiring direct manipulation of raw native pointers or C/C++ -memory management, use [FFIgen](/platform-integration/interop/ffigen) or +For specialized use cases that require direct manipulation of raw native +pointers or manual C and C++ memory management, +use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. ### Inheritance -Pigeon does not support complex inheritance. Flatten your data models where possible. +Pigeon doesn't support complex class inheritance. +Flatten your data models where possible. -### (Pigeon with platform channels only) Async errors +### Asynchronous errors on platform channels -For `@async` methods, native code must return errors through the provided callback. +When using Pigeon with platform channels, native implementations of `@async` +methods must return errors through the provided callback. ## Resources -* [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-migration-guide] -* [Pigeon native interop guide][pigeon-native-guide] -* [Pigeon package on pub.dev][pigeon-pub] -* [Pigeon source code][pigeon-source] -* [Flutter native interop demos][native-interop-demos] - -[pigeon-pub]: https://pub.dev/packages/pigeon -[pigeon-source]: https://github.com/flutter/packages/tree/main/packages/pigeon -[pigeon-example]: https://github.com/flutter/packages/tree/main/packages/pigeon/example -[pigeon-native-example]: https://github.com/flutter/packages/tree/main/packages/pigeon/example/native_interop_app -[pigeon-native-guide]: https://github.com/flutter/packages/blob/main/packages/pigeon/native_interop_guide.md -[pigeon-migration-guide]: https://github.com/flutter/packages/blob/main/packages/pigeon/native_interop_migration_guide.md -[shared-preferences-source]: https://github.com/flutter/packages/tree/main/packages/shared_preferences -[native-interop-demos]: https://github.com/flutter/demos/tree/main/native_interop_demos +To learn about building and migrating plugins with Pigeon, +consult the following resources: + +- [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-migration-guide] +- [Pigeon native interop guide][pigeon-native-guide] +- [`package:pigeon` on pub.dev][pigeon-pub] +- [Pigeon source code][pigeon-source] +- [Flutter native interop demos][native-interop-demos] + +[Dart isolates]: {{site.dart-site}}/language/isolates +[pigeon-pub]: {{site.pub-pkg}}/pigeon +[pigeon-source]: {{site.repo.packages}}/tree/main/packages/pigeon +[pigeon-example]: {{site.repo.packages}}/tree/main/packages/pigeon/example +[pigeon-native-example]: {{site.repo.packages}}/tree/main/packages/pigeon/example/native_interop_app +[pigeon-native-guide]: {{site.repo.packages}}/blob/main/packages/pigeon/native_interop_guide.md +[pigeon-migration-guide]: {{site.repo.packages}}/blob/main/packages/pigeon/native_interop_migration_guide.md +[shared-preferences-source]: {{site.repo.packages}}/tree/main/packages/shared_preferences +[native-interop-demos]: {{site.repo.demos}}/tree/main/native_interop_demos diff --git a/sites/docs/src/content/platform-integration/platform-channels.md b/sites/docs/src/content/platform-integration/platform-channels.md index 319b2a64b55..bb86fc8ee1f 100644 --- a/sites/docs/src/content/platform-integration/platform-channels.md +++ b/sites/docs/src/content/platform-integration/platform-channels.md @@ -7,12 +7,12 @@ description: Learn how to write platform-specific code using Flutter's platform :::note -Refer to the [Native interop landing page](/platform-integration/interop) -for an overview and recommended approaches for calling native platform code. +Refer to the [Native interop overview](/platform-integration/interop) +for recommended approaches to calling native platform code. ::: Platform channels pass messages between Flutter and your host platform -(Android, iOS, macOS, Windows, Linux, or Web). +(Android, iOS, macOS, Windows, or Linux). While [Pigeon](/platform-integration/interop/pigeon), [FFIgen](/platform-integration/interop/ffigen), and @@ -25,13 +25,13 @@ maintaining existing codebases. You can use platform-specific code in your Flutter app. Common ways to do this include: -* Use Flutter's platform channel APIs to pass messages - between Flutter and your desired platforms. For more - details, refer to [Call platform-specific code using platform channels](#example). +- Use Flutter's platform channel APIs to pass messages + between Flutter and your target platforms. + For details, refer to + [Call platform-specific code using platform channels](#example). -* Use the `Pigeon` package to generate type-safe - platform-specific code. For more - details, refer to [Pigeon](/platform-integration/interop/pigeon). +- Use [Pigeon](/platform-integration/interop/pigeon) to generate type-safe + platform-specific bindings. Flutter supports the following platforms and platform-specific languages: From 8c10761c89a138b7d0b7e1efad7e3f68ad462e0c Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 20 Oct 2026 05:26:50 +0000 Subject: [PATCH 05/16] Anchor interop docs closely to EAP text and refine index layout --- .../platform-integration/interop/dart-ffi.md | 66 +++---- .../platform-integration/interop/ffigen.md | 145 ++++++-------- .../platform-integration/interop/index.md | 187 +++++++++--------- .../platform-integration/interop/jnigen.md | 134 ++++++------- .../platform-integration/interop/pigeon.md | 112 +++++------ 5 files changed, 295 insertions(+), 349 deletions(-) diff --git a/sites/docs/src/content/platform-integration/interop/dart-ffi.md b/sites/docs/src/content/platform-integration/interop/dart-ffi.md index fdcb0164123..0197ac373b8 100644 --- a/sites/docs/src/content/platform-integration/interop/dart-ffi.md +++ b/sites/docs/src/content/platform-integration/interop/dart-ffi.md @@ -6,58 +6,56 @@ description: >- --- The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library -that provides low-level APIs to call C-ABI functions and manage native memory -from Dart. +that provides the low-level APIs to call C-ABI functions from Dart. -Unlike code generation tools such as [Pigeon](/platform-integration/interop/pigeon), +Rather than a code generation tool like [Pigeon](/platform-integration/interop/pigeon), [FFIgen](/platform-integration/interop/ffigen), or [JNIgen](/platform-integration/interop/jnigen), -`dart:ffi` is the underlying runtime layer that powers those tools -and enables direct native memory interop. +`dart:ffi` is the underlying technology that powers these tools +and enables native memory interop. ## When to use `dart:ffi` -When you use FFIgen or JNIgen to generate Dart bindings for low-level APIs, -the generated bindings often take native pointers or C structs -rather than Dart objects like `String` or `List`. -Because Dart memory is garbage collected +When you use FFIgen or JNIgen to generate Dart bindings, +the generated code does not automatically convert Dart objects +like `String` or `List` into native formats. +Because Dart memory is automatically garbage collected and native C memory is manually managed, -you use `dart:ffi` and `package:ffi` to bridge the two memory models: +you must use `dart:ffi` to bridge this gap. -- **Memory allocation.** - Allocate space on the native heap for C pointers, structs, and buffers, - and free that memory when the native call finishes to prevent memory leaks. -- **Data conversion.** - Convert Dart types (such as `String` or typed data lists) into C-compatible - representations (such as `Pointer`) that native code can read. +The two most common scenarios when `dart:ffi` is needed are +memory allocation and data conversion. -## Core libraries: `dart:ffi` and `package:ffi` +With memory allocation, you use `dart:ffi` to allocate space on the system heap +for native arguments, and you must manually free that memory when done. +This helps avoid memory leaks. -To work with native memory in Dart, you use two libraries together: +With data conversion, you use `dart:ffi` to convert Dart data types +so they can be read by native code. -- **`dart:ffi`.** - Built into the core Dart SDK. - Provides native representation types (`Pointer`, `Struct`, `Union`, `Int32`), - `@Native` annotations, and dynamic library lookup APIs. -- **`package:ffi`.** - A companion package on pub.dev that provides native memory allocators - (`calloc`, `malloc`, and `Arena`) and string conversion helpers - (`toNativeUtf8`, `toDartString`). +## Core tools: `dart:ffi` and `package:ffi` + +To write interop code, you use two libraries: + +- **`dart:ffi`:** Built into the core Dart SDK. + It provides native representation types and lookup signatures. +- **`package:ffi`:** A required dependency for working with `dart:ffi`. + It provides memory allocation methods and conversion helpers. ## Concurrency in Flutter -FFI calls execute synchronously on the calling thread. -If you invoke a long-running or blocking C function on your Flutter app's -main UI isolate, your app drops frames or freezes. -To keep the UI responsive, run blocking FFI calls inside a background -Dart isolate using `Isolate.run`. +FFI calls are synchronous and run on the thread they are called on. +If you invoke a heavy or blocking C function on your Flutter app's +main UI thread, your app freezes or drops frames. +To prevent this, execute blocking FFI calls inside a background Dart isolate +using `Isolate.run`. ## Resources -To explore code examples for memory allocation, string conversion, -structs, and dynamic library loading, consult the following resources: +To explore examples of memory allocation, string conversion, +and dynamic library loading, refer to these Dart interop resources: -- [C interop guide on dart.dev][dart-c-interop] +- [C interop guide (includes code examples for pointers, memory allocation, and structs)][dart-c-interop] - [`dart:ffi` API reference][dart-ffi-api] - [`package:ffi` on pub.dev][package-ffi-pub] diff --git a/sites/docs/src/content/platform-integration/interop/ffigen.md b/sites/docs/src/content/platform-integration/interop/ffigen.md index e29d518442b..1e697138b87 100644 --- a/sites/docs/src/content/platform-integration/interop/ffigen.md +++ b/sites/docs/src/content/platform-integration/interop/ffigen.md @@ -7,149 +7,134 @@ description: >- ## Overview -FFIgen (Foreign Function Interface generator) is a code generator tool +FFIgen (the Foreign Function Interface generator) is a code generator tool that creates type-safe Dart bindings for C libraries and Apple frameworks -(Objective-C and `@objc`-annotated Swift). -With FFIgen, you can call C, iOS, and macOS APIs directly and synchronously -from your Dart code. +(Objective-C and Swift). +This allows you to call Apple platform APIs (iOS and macOS) directly +and synchronously from your Dart code. :::note -Direct Swift interop without Objective-C compatibility annotations (`@objc`) -is experimental and uses [`package:swiftgen`][swiftgen-pub]. -For production Swift plugins on iOS and macOS, -use [Pigeon](/platform-integration/interop/pigeon). +Direct Swift interop is in experimental support. ::: ## When to use FFIgen Use FFIgen when you need to call C libraries, -wrap compiled C-ABI binaries (from C, C++, Rust, or Go), -or interact directly with Objective-C frameworks on iOS and macOS. +wrap compiled binaries (like C, C++, Rust, or Go), +or interact directly with Objective-C and Swift frameworks on iOS and macOS. :::note FFIgen parses C and Objective-C headers. -To wrap a C++ library, expose a C-compatible interface (`extern "C"`) +To wrap C++ libraries, you must expose a C-compatible interface around your C++ code. -FFIgen can't parse C++ classes or templates directly. +FFIgen cannot parse C++ classes or templates directly. ::: -For cross-platform plugin development on Android, iOS, and macOS -where you don't need direct access to raw C pointers, -use [Pigeon](/platform-integration/interop/pigeon) instead. +For standard, cross-platform plugin development where you don't have +critical data transfers, use [Pigeon](/platform-integration/interop/pigeon) +instead. ## Developer flow -Generating bindings with FFIgen follows four steps: +Generating bindings with FFIgen follows these steps: 1. Reference the native headers. 1. Write the FFIgen configuration script. -1. Generate the bindings by running the configuration script. +1. Generate the bindings by running FFIgen. 1. Call the generated code from Dart. -To start, identify the native header files or Apple framework APIs -that you want to wrap. -Then create a configuration script in `tool/ffigen.dart` -(some existing templates, such as `flutter create --template=package_ffi`, -use a `ffigen.yaml` file). +To start, identify the native header files or APIs you want to wrap. +Then write a configuration script (typically `tool/ffigen.dart` or +`ffigen.yaml`). This configuration specifies where the header files are located -and where to write the generated Dart bindings. +and where to output the generated Dart code. -Next, run the configuration script (`dart run tool/ffigen.dart`, -or `dart run ffigen` if your project uses `ffigen.yaml`), -which parses the headers and generates the corresponding Dart bindings. +Next, run the generator tool (such as `dart run tool/ffigen.dart` or +`dart run ffigen`), which parses the headers and generates the corresponding +Dart bindings. -Finally, import the generated Dart file into your app or package -and call the functions directly. -Because FFIgen communicates directly through the underlying FFI layer, -consult the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) -to learn how to manage native memory lifetimes -and run blocking native calls without freezing the UI thread. +Last, import the generated Dart file into your app and call the functions +directly. +Because FFIgen communicates directly using the underlying FFI layer, +refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +to learn how to manage native memory lifetimes and execute native calls +without blocking the UI thread. :::note -When the native API changes, update your configuration script +If the native API changes, you need to update your configuration script and regenerate the bindings. ::: -## Examples +## Example -To explore complete implementations of this workflow, +To explore a complete implementation of this flow, including how to configure the generator, -refer to the [official FFIgen examples][ffigen-examples]: +refer to the [official FFIgen examples][ffigen-examples]. -- The `simple` example demonstrates the general workflow with a minimal C API - (a single function in one header file). -- The `objective_c` and `swift` examples demonstrate calling Apple frameworks - using `tool/ffigen.dart`. -- The [`code_assets` examples][code-assets-examples] demonstrate real-world - bundling use cases, such as system hostname lookups and audio playback. +The Add example highlights the general flow with a toy native API +(one function in a single header file). + +The `code_assets` examples highlight real-world examples like +system hostname lookups and audio playback. ## Constraints and limitations -Keep the following general considerations in mind when working with FFIgen: +Some general considerations when working with FFIgen: ### Source header files FFIgen uses `libclang` to parse headers. -Resolve any compiler warnings logged during generation -to prevent unexpected runtime crashes. +Always resolve compiler warnings logged during generation +to prevent runtime crashes. + +### OS differences -### Operating system differences +Dart has no equivalent of C macros (like `#if TARGET_OS_IPHONE`), +so you must handle minor API variations using runtime `Platform.isIOS` checks. -Dart has no preprocessor equivalent to C macros -(such as `#if TARGET_OS_IPHONE`), -so handle platform-specific API variations at runtime -using `Platform.isIOS` or `Platform.isMacOS` checks. +Some Objective-C and Swift-specific pitfalls when working with FFIgen: ### Objective-C bindings -Objective-C methods are resolved dynamically at runtime. -FFIgen maps Objective-C selectors to Dart method signatures automatically, -but inspect the generated bindings if you wrap heavily overloaded -native methods. +Objective-C methods are dynamically resolved. +FFIgen automatically maps method selectors to Dart signatures, +but you should verify selectors by reading the bindings +if you are using heavily overloaded native methods. ### Objective-C memory management -Dart uses garbage collection, whereas Objective-C uses reference counting. -Passing Dart closures that capture native wrapper objects can create -reference cycles across runtimes and leak memory. -To avoid retain cycles, consult the -[Objective-C memory management guide][objc-gc-doc]. +Dart uses garbage collection while Objective-C uses reference counting. +Passing Dart closures that close over native wrapper objects can easily create +reference cycles and leak memory. -### Objective-C runtime type checks +### Objective-C runtime checks -FFIgen wrappers enforce static Dart types, -but the underlying Objective-C object might be a different subclass at runtime. -Because of this, Dart's `is` and `as` keywords don't check the Objective-C -runtime type. +FFIgen wrappers enforce Dart static types, +but the underlying Objective-C object might have a different runtime subclass, +so Dart's `is` and `as` keywords won't work as expected. Use `.isA()` and `.as()` instead. ### Objective-C multithreading -UI-related Objective-C calls must run on the platform's main thread. -Some native callbacks can also crash your app if invoked on the wrong thread. +UI-related Objective-C calls must run on the iOS main platform thread. +Also, some types of callbacks can crash your app +if they are executed on the wrong thread. ### Swift compatibility -FFIgen interacts with Swift through Objective-C compatibility headers. -Swift classes and methods must be annotated with `@objc` (or `@objcMembers`) -and inherit from `NSObject`. -Pure Swift features without an Objective-C representation—such as Swift structs, -generics, and enums with associated values—aren't supported by FFIgen. -For Swift plugins, use [Pigeon](/platform-integration/interop/pigeon), -or explore the experimental [`package:swiftgen`][swiftgen-pub] package. +FFIgen interacts with Swift through Objective-C. +Pure Swift features (such as structs, generics, or enums associated with values) +are not supported, and classes must be annotated with `@objc`. +For pure Swift interop, explore the experimental [`package:swiftgen`][swiftgen-pub] +package. ## Resources -To learn about generating C and Objective-C bindings, -consult the following resources: - -- [`package:ffigen` on pub.dev][ffigen-pub] -- [`package:objective_c` on pub.dev][objc-pub] +- [FFIgen package on pub.dev][ffigen-pub] +- [`objective_c` package on pub.dev][objc-pub] - [Official FFIgen examples on GitHub][ffigen-examples] [ffigen-pub]: {{site.pub-pkg}}/ffigen [objc-pub]: {{site.pub-pkg}}/objective_c [swiftgen-pub]: {{site.pub-pkg}}/swiftgen [ffigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/ffigen/example -[code-assets-examples]: {{site.pub-pkg}}/code_assets/example -[objc-gc-doc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_gc.md diff --git a/sites/docs/src/content/platform-integration/interop/index.md b/sites/docs/src/content/platform-integration/interop/index.md index 16e208d8f2a..5c590416be1 100644 --- a/sites/docs/src/content/platform-integration/interop/index.md +++ b/sites/docs/src/content/platform-integration/interop/index.md @@ -4,14 +4,14 @@ description: Learn about the options to call platform-specific code in your app. --- Flutter apps run on multiple platforms, -but sometimes you need to communicate directly with the host operating system -or call code from an existing native library. +but sometimes you need to communicate directly +with the host operating system or run code from an existing native library. -To support these use cases, Flutter and Dart provide interop tools -that range from high-level, type-safe code generators to low-level FFI bindings. +To solve this, Flutter provides several interop tools ranging from +code generators to direct FFI bindings. Use the following decision tree to choose your interop solution, -or read on to learn how to package and structure native interop code. +or read on to learn about the native interop layers. ## Choose your interop solution @@ -32,164 +32,155 @@ flowchart TD classDef directNode stroke:#1a73e8,stroke-width:2px; ``` -### Step 1: Determine your primary goal +--- + +Follow the steps below to walk through the decision tree in detail: -Identify which of the following goals matches your project: +### Step 1: Determine your primary goal - **Goal A: Platform integration.** - Communicate with native operating system APIs - (Swift, Kotlin, Java, or Objective-C) or expose a plugin API surface. + Communicate with native OS features (Swift, Kotlin, Java, or Objective-C) + or expose an API surface. Proceed to [Step 2: Evaluate Pigeon for Flutter plugins](#step-2-evaluate-pigeon-for-flutter-plugins). - **Goal B: C-ABI wrapping.** - Wrap an existing native library written in C, C++ (with `extern "C"`), - Rust, or Go. - **Recommendation:** Use [FFIgen](/platform-integration/interop/ffigen) + Wrap an existing native library (C, C++, Rust, or Go). + Use [FFIgen](/platform-integration/interop/ffigen) (or [Flutter Rust Bridge][frb] for Rust). - **Goal C: Pure Dart package.** - Build a standalone Dart CLI tool or backend package without Flutter framework - dependencies. - **Recommendation:** Use [FFIgen](/platform-integration/interop/ffigen) or + Build a Dart CLI tool without Flutter framework dependencies. + Use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen). ### Step 2: Evaluate Pigeon for Flutter plugins -If you're building a Flutter plugin or calling host platform APIs, -verify whether [Pigeon](/platform-integration/interop/pigeon) fits your -technical constraints before choosing low-level direct bindings. +If you are building a Flutter plugin, +first verify whether [Pigeon](/platform-integration/interop/pigeon) satisfies +your technical constraints. :::important -Migrating a plugin between Pigeon and FFIgen or JNIgen requires rewriting your -integration. -With Pigeon, you define APIs in Dart, generate native interfaces, -and write glue code in native languages (Kotlin, Swift, or C++). -With FFIgen and JNIgen, you generate Dart bindings from native APIs -and write glue code in Dart. -Because the glue code lives in different languages and architectural layers, -choose your interop tool carefully based on your project's constraints. +Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial. +Choose your interop solution carefully based on your initial project +constraints. ::: -Check whether any of the following advanced requirements apply to your feature: +Do any of the following requirements apply to your feature? -- Passing large volumes of raw data using native pointers - (for example, custom video frame buffers). -- Advanced cross-language object bridging - (such as cross-language subclasses, delegates, or proxies). -- Manual memory management and custom multithreading instead of automated +- Your app processes high-frequency raw data (for example, video processing). +- Your integration requires advanced language bridging + (for example, cross-language subclasses, delegates, or proxies). +- Your feature requires manual memory management and multithreading over guardrails. -- Calling C, C++, Rust, or Go libraries directly. - -Based on those requirements, choose one of the following paths: +- Your target language is C, C++, Rust, or Go. - **No (most common): Use [Pigeon](/platform-integration/interop/pigeon).** - Pigeon is the recommended default path for Flutter plugins. - It provides type-safe contracts, handles memory lifetimes and data conversion, - and calls Swift and Kotlin directly through FFI and JNI on Android, iOS, - and macOS. + This is the default, recommended path. + It provides necessary guardrails, creates easily digestible interface + mappings, and is highly efficient for most API surface areas. - **Yes: Use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen).** - Direct interop generators give you full control over large native API surfaces - and raw memory. + You need the flexibility, complexity, and performance of direct interop for + larger surface areas. :::note -Using FFIgen and JNIgen directly requires managing -[threading][jnigen-threading] and [native memory lifetimes][objc-gc] yourself. -Pigeon is recommended for most plugins because it handles these concerns -automatically. +Directly using FFIgen and JNIgen introduces potential pitfalls, +particularly around threading and memory management. +Pigeon is generally recommended because it has fewer potential pitfalls +and provides safer guardrails. ::: ## Package your interop code -Depending on how you plan to organize and share your code, -choose the packaging option that fits your use case. -To learn about the difference between a Dart package and a Flutter plugin, +Depending on how you plan to share your code, +choose the option that best suits your use case. + +:::note +To learn about the difference between a package and a plugin, refer to [Using packages](/packages-and-plugins/using-packages). +::: -- **[In-app integration](/platform-integration/platform-channels).** - Write native interop code directly inside your Flutter app without creating - a separate package. -- **[Plugin package](/packages-and-plugins/developing-packages#plugin).** - Create a specialized package that exposes platform-specific functionality - (such as camera or sensor APIs) to Flutter apps +- **In-app integration.** + Write your native interop code directly inside your Flutter app without + creating a separate package. +- **Plugin.** + A specialized package that makes native platform functionality (such as a + device's camera or sensors) available to your app (`flutter create --template=plugin`). -- **[FFI package](/packages-and-plugins/developing-packages#ffi).** - Create a package that compiles and bundles native C or C++ code using - [Dart build hooks][dart-hooks] and [`dart:ffi`](/platform-integration/interop/dart-ffi), - and includes an [FFIgen](/platform-integration/interop/ffigen) configuration - to regenerate Dart bindings (`flutter create --template=package_ffi`). + For more details, refer to + [Developing plugin packages](/packages-and-plugins/developing-packages#plugin). +- **FFI package.** + A package that compiles and bundles native C or C++ code using Dart build + hooks and [`dart:ffi`](/platform-integration/interop/dart-ffi). + This template also includes an `ffigen.yaml` configuration file to regenerate + Dart bindings (`flutter create --template=package_ffi`). + For more details, refer to + [Developing FFI packages](/packages-and-plugins/developing-packages#ffi). :::note -If you're bundling pre-compiled binaries (such as `.so` files on Android -or `.dylib` files on iOS and macOS), use `CodeAsset` in your `hook/build.dart` +If you're using pre-compiled binaries (such as `.so` files on Android or +`.dylib` files on iOS and macOS), use `CodeAsset` in your `hook/build.dart` script to bundle them into your package. -For details, refer to -[Bind to native code using FFI](/platform-integration/interop/bind-native-code#closed-source-libraries). ::: -To learn about authoring packages, -consult [Developing packages and plugins](/packages-and-plugins/developing-packages). +For more details on package authoring, +refer to [Developing packages and plugins](/packages-and-plugins/developing-packages). ## Tool comparison -The following table compares direct interop generators (FFIgen and JNIgen) -and Pigeon across target languages: +The following table compares the two interop approaches across target +languages: + +- **Direct interop:** Lower-level direct bindings with higher performance for + large data; requires manual memory and threading management. +- **Pigeon:** Structured, type-safe code generator; recommended default for + Flutter plugins. | Target language | Direct interop | Pigeon | | --- | --- | --- | -| **C** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (supported with platform channels, but not maintained by the Flutter team) | -| **C++ with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (refer to C) | -| **C++ without `extern "C"`** | Not supported | Supported on Windows with platform channels | -| **Rust with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (refer to C) | +| **C** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended. Supported with platform channels, but not maintained by the Flutter team. | +| **C++ with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **C++ without `extern "C"`** | FFIgen (not yet supported) | Supported with platform channels, but not maintained by the Flutter team. | +| **Rust with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | | **Rust without `extern "C"`** | [Flutter Rust Bridge][frb] | Not supported | -| **Go with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended (refer to C) | +| **Go with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | | **Go without `extern "C"`** | Not supported | Not supported | | **Java** | [JNIgen](/platform-integration/interop/jnigen) | Supported through Kotlin | | **Kotlin** | [JNIgen](/platform-integration/interop/jnigen) | Supported (recommended) | | **Objective-C** | [FFIgen](/platform-integration/interop/ffigen) | Supported through Swift | -| **Swift with `@objc` annotations** | [FFIgen](/platform-integration/interop/ffigen) | Supported (recommended) | -| **Swift without `@objc` annotations** | [`package:swiftgen`][swiftgen-pub] (experimental) | Supported (recommended) | - -{:.table .table-striped} - -- **Direct interop:** Generates low-level bindings directly against native - headers or classes; requires manual memory and threading management. -- **Pigeon:** Generates structured, type-safe API contracts between Dart and the - host platform; recommended default for Flutter plugins. +| **Swift without Obj-C compatibility annotations** | [`package:swiftgen`][swiftgen-pub] (experimental) | Supported (recommended) | ## Understand interop layers -Dart structures native interop into three architectural layers: +Dart structures native interop into three distinct layers: low-level bindings, idiomatic Dart APIs, and framework-specific plugins. + For complete architectural details, consult the [native interop layers guide][interop-layers]. ### Layer 1: Low-level bindings -Layer 1 consists of thin, unopinionated wrappers around native APIs -(such as [`dart:ffi`](/platform-integration/interop/dart-ffi), +Layer 1 consists of thin, high-performance, and unopinionated wrappers around +native code (for example, [`dart:ffi`](/platform-integration/interop/dart-ffi), `dart:js_interop`, [FFIgen](/platform-integration/interop/ffigen), and [JNIgen](/platform-integration/interop/jnigen)). -These bindings mirror native signatures closely, -but require you to manage memory and threading manually. +These APIs mirror native signatures closely, +but require manual memory and threading management. ### Layer 2: Idiomatic Dart APIs -Layer 2 wraps the low-level bindings in idiomatic Dart APIs to hide -platform-specific details (for example, [Pigeon](/platform-integration/interop/pigeon) -or [`package:sqlite3`][sqlite3-pub]). -This layer manages concurrency, memory lifetimes, and data conversions -automatically. +Layer 2 consists of idiomatic Dart APIs that wrap the low-level layer to hide +platform-specific complexities (for example, +[Pigeon](/platform-integration/interop/pigeon) or the +[`sqlite3` package][sqlite3-pub]). +These manage concurrency, memory lifetimes, and data conversions automatically. ### Layer 3: Framework-specific plugins -Layer 3 integrates with framework-specific lifecycle and UI APIs -(such as the Flutter Plugin API). -This layer exposes clean Dart APIs to your Flutter app while encapsulating -platform objects like Android's `Context` or iOS's application lifecycle. +Layer 3 provides integration with framework-specific APIs (for example, the +Flutter Plugin API). +This layer exposes Dart APIs to the framework while hiding platform details +like Android's `Context` or iOS's application lifecycle. [frb]: {{site.pub-pkg}}/flutter_rust_bridge [swiftgen-pub]: {{site.pub-pkg}}/swiftgen [sqlite3-pub]: {{site.pub-pkg}}/sqlite3 -[dart-hooks]: {{site.dart-site}}/tools/hooks -[jnigen-threading]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/threading.md -[objc-gc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_gc.md [interop-layers]: https://github.com/dart-lang/native/blob/main/doc/interop-layers.md diff --git a/sites/docs/src/content/platform-integration/interop/jnigen.md b/sites/docs/src/content/platform-integration/interop/jnigen.md index 4e8bb270a08..b4d3ce559dd 100644 --- a/sites/docs/src/content/platform-integration/interop/jnigen.md +++ b/sites/docs/src/content/platform-integration/interop/jnigen.md @@ -7,117 +7,113 @@ description: >- ## Overview -JNIgen (Java Native Interface generator) is a code generator tool -that creates type-safe Dart bindings for Java and Kotlin libraries and APIs. -With JNIgen, you can call Android APIs, JARs, and AARs directly -and synchronously from your Dart code. +JNIgen (Java Native Interface generator) is a code generator tool that creates +type-safe Dart bindings for Java and Kotlin libraries and APIs. +This allows you to call Android APIs directly and synchronously from your +Dart code. ## When to use JNIgen -Use JNIgen when you need to call Java or Kotlin libraries directly from Dart, -wrap compiled JAR or AAR files, -or build a standalone Dart package that interacts with the JVM. +Use JNIgen when you need to call Java or Kotlin libraries, wrap compiled JAR +or AAR files, or interact directly with Android from your Dart code. -Because JNIgen calls into the JVM directly through `dart:ffi`, -it's also suited for high-performance, low-latency interop -or passing large volumes of raw data between Dart and the JVM. +Because JNIgen uses direct JNI bindings through `dart:ffi`, +it is also the recommended tool when you require high-performance, +low-latency interop, or need to pass massive amounts of raw data between Dart +and the JVM. -For standard Android plugin development where you don't need direct access -to raw JNI references, use [Pigeon](/platform-integration/interop/pigeon) -with native interop (`useJni: true`) instead. +For standard, cross-platform plugin development where you don't have +critical data transfers, use [Pigeon](/platform-integration/interop/pigeon) +instead. ## Developer flow -Generating bindings with JNIgen follows four steps: +Generating bindings with JNIgen follows this general flow: -1. Reference the native classes, JARs, or AARs. +1. Reference the native classes or JARs. 1. Write the JNIgen configuration script. -1. Generate the bindings by running the configuration script. +1. Generate the bindings by running JNIgen. 1. Call the generated code from Dart. -To start, identify the Java or Kotlin classes, packages, -or compiled JAR and AAR files that you want to wrap. -Then create a configuration script in `tool/jnigen.dart` -(some older projects use a legacy `jnigen.yaml` file). -This script specifies where the native files are located, -which classes or packages to include, -and where to output the generated Dart bindings. -For a complete list of configuration options, -consult the [`package:jnigen` documentation on pub.dev][jnigen-pub]. - -Next, run the configuration script (`dart run tool/jnigen.dart`, -or `dart run jnigen --config jnigen.yaml` if your project uses `jnigen.yaml`), +:::note +For a complete list of configuration options, consult the +[JNIgen documentation on pub.dev][jnigen-pub] and the JNIgen README. +::: + +To start, identify the native Java classes, packages, or compiled JAR/AAR files +you want to wrap. +Then write a configuration script (typically `tool/jnigen.dart` or +`jnigen.yaml`). +This configuration specifies where the native files are located, +what packages to include, and where to output the generated Dart code. + +Next, run the generator tool (such as `dart run tool/jnigen.dart` or +`dart run jnigen --config jnigen.yaml`), which parses the classes and generates the corresponding Dart bindings. -Finally, import the generated Dart file into your app or package -and call the methods directly. -Because JNIgen communicates directly through the underlying FFI layer, -consult the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) -to learn how to manage native memory lifetimes -and run blocking native calls without freezing the UI thread. +Last, import the generated Dart file into your app and call the functions +directly. +Because JNIgen communicates directly using the underlying FFI layer, +refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) to learn +how to manage native memory lifetimes and execute native calls without blocking +the UI thread. :::note -When the native API changes, update your configuration script -and regenerate the bindings. +If the native API changes, you need to update your configuration script and +regenerate the bindings. ::: ## Examples -To explore complete implementations of this workflow, +To explore complete implementations of this flow, including how to configure the generator, -refer to the [`permissions_plugin` demo][permissions-demo] -and the [`android_toast_demo`][toast-demo] -in the Flutter native interop demos repository. +refer to the [`permissions_plugin` demo][permissions-demo] and the +[`android_toast_demo`][toast-demo] in the official Flutter native interop +demos repository. ## Constraints and limitations -Keep the following considerations and limitations in mind -when working with JNIgen: +Keep these general considerations and limitations in mind when working with +JNIgen: ### Threading considerations JNIgen calls execute synchronously on the calling thread. Watch out for two common scenarios: -- **Platform thread APIs.** - To call Android APIs that must run on the platform main thread, - use `runOnPlatformThread` from `dart:ui`. -- **Cross-thread callbacks.** - Dart callbacks invoked from Java or Kotlin run on the Dart isolate - that created them. - Avoid blocking a Java thread while waiting for a Dart isolate that is itself - blocked waiting on that Java thread, which causes a deadlock. +- Calling platform APIs that must run on the platform thread. + Use `runOnPlatformThread` from `dart:ui`. +- Dart callbacks from Java run on the isolate that created them. + This can lead to deadlocks if a blocked Java thread waits for a blocked + Dart thread. ### Lifecycle of Java objects -JNI wrappers hold Java global references, -which have an Android runtime limit of approximately 50,000 active references. -Call `.release()` or scope objects inside `.using()` blocks -to free references promptly and prevent memory leaks. -When implementing Java interfaces in Dart, use `WeakReference` -to avoid reference cycles between the Dart and JVM garbage collectors. +JNI wrappers use Java global references. +There is a hard limit of 50,000 active references. +Call `.release()` or use `.using()` blocks to prevent memory leaks. +Use `WeakReference` to break reference cycles between Dart and Java GCs +when implementing interfaces. ### JVM runtime type checks -JNI wrappers are Dart extension types that compile to `JObject` at runtime. -Because of this, Dart's `is` and `as` keywords don't check -the Java runtime type. +JNI wrappers are Dart extension types, and they compile down to `JObject` +at runtime. +Because of this, Dart's native `is` and `as` keywords do not check the Java +runtime type. Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. -### Debugging reference errors +### General debugging -To trace JNI reference lifecycle bugs such as `DoubleReleaseError` -and `UseAfterReleaseError`, -set `Jni.captureStackTraceOnRelease = true` to capture allocation and release -stack traces. +Tracking down JNI memory allocation bugs like `DoubleReleaseError` +and `UseAfterReleaseError` can be difficult. +Set `Jni.captureStackTraceOnRelease = true` to log stack traces +and help trace leaks. ## Resources -To learn about generating Java and Kotlin bindings, -consult the following resources: - -- [`package:jnigen` on pub.dev][jnigen-pub] -- [`package:jni` on pub.dev][jni-pub] +- [JNIgen package on pub.dev][jnigen-pub] +- [JNI package on pub.dev][jni-pub] - [Official JNIgen examples on GitHub][jnigen-examples] [jnigen-pub]: {{site.pub-pkg}}/jnigen diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/platform-integration/interop/pigeon.md index b26fd773ecf..a20f7762976 100644 --- a/sites/docs/src/content/platform-integration/interop/pigeon.md +++ b/sites/docs/src/content/platform-integration/interop/pigeon.md @@ -8,15 +8,15 @@ description: >- ## Overview Pigeon is the recommended default tool for building Flutter plugins. -Pigeon replaces the manual setup of raw platform channels -with generated, type-safe APIs, -which prevents typos in method names and mismatched argument types. +Pigeon replaces the manual setup of raw platform channels with generated, +type-safe APIs. +This helps prevent typos in method names or mismatched argument types. -## Choose a communication option +## Choose a communication option with Pigeon -Pigeon supports two communication options depending on your target platform -and performance requirements: -native interop (direct FFI and JNI) and platform channels. +Pigeon supports the following two communication options depending on your target +platform and performance needs: +native interop (direct FFI and JNI) or platform channels. :::note For low-level integrations that require direct access to native memory @@ -24,123 +24,99 @@ or C and C++ libraries, use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. ::: -### Pigeon with native interop (recommended for Android, iOS, and macOS) +### Pigeon with native interop -Pigeon with native interop generates direct native bindings +Pigeon with native interop (direct FFI and JNI) generates direct native bindings using FFI on iOS and macOS, and JNI on Android. -This is the recommended default when targeting Android, iOS, or macOS. -Use this option when building for Android, iOS, or macOS, -especially if your use case requires any of the following capabilities: +Use this option if your use case requires any of the following: -- Synchronous execution on the native platform. -- High-frequency data streaming (for example, audio or camera frames). -- Calling native code directly from background [Dart isolates][]. +- You need synchronous execution on your native platform. +- You are doing high-frequency data streaming + (for example, audio or camera frames). +- You are calling native code from background Dart isolates. ### Pigeon with platform channels Pigeon with platform channels generates standard platform channels that support Android, iOS, macOS, Windows, and Linux. -You can generate platform channels for Windows and Linux from the same -Pigeon definition file that uses native interop on Android, iOS, and macOS. -Use this option if your use case requires any of the following capabilities: +Use this option if your use case requires any of the following: -- Desktop support on Windows or Linux. -- Zero external toolchains. +- You need desktop support on Windows or Linux. +- You want zero external toolchains. ## Developer flow -The developer workflow with Pigeon follows four steps: +The developer experience with Pigeon follows a structured cycle: 1. Create the Pigeon definition file. -1. Generate the bindings. +1. Generate the code. 1. Implement the native interface. 1. Call the API from Flutter. To start, create a Pigeon definition file in Dart (typically outside of your `lib/` directory, such as `pigeons/messages.dart`). -This file defines your data structures and API interfaces using Pigeon -annotations such as `@HostApi` and `@FlutterApi`. -Next, run the Pigeon generator tool (`dart run pigeon`), -which parses your Pigeon definition file and generates code for both Dart -and your target native platforms. - -To turn on native interop, set `appDirectory`, `swiftOptions`, -and `kotlinOptions` in the `@ConfigurePigeon` annotation at the top of your -Pigeon definition file: - -```dart title="pigeons/messages.dart" -@ConfigurePigeon( - PigeonOptions( - // Tells FFIgen and JNIgen where your compiled app lives - // (use './' for an app, or 'example/' for a plugin). - appDirectory: 'example/', - // iOS and macOS: call your Swift implementation directly through FFI. - swiftOptions: SwiftOptions(useFfi: true), - // Android: call your Kotlin implementation directly through JNI. - kotlinOptions: KotlinOptions(useJni: true), - ), -) -``` +This file defines the data structures and the API interfaces +using Pigeon annotations like `@HostApi` and `@FlutterApi`. +Next, run the Pigeon generator tool, which parses your Pigeon definition file +and produces the necessary code for both Dart and your target native platform. +:::note +If you're using the Pigeon with native interop communication option, +set `appDirectory`, `swiftOptions: SwiftOptions(useFfi: true)`, and +`kotlinOptions: KotlinOptions(useJni: true)` in the `@ConfigurePigeon` +section of your Pigeon definition file. For complete prerequisites and build configuration, -consult the [Pigeon native interop guide][pigeon-native-guide]. +refer to the [native interop guide][pigeon-native-guide]. +::: -After generating the code, implement the generated native interfaces -in your app's or plugin's native platform directory -(such as `android/`, `ios/`, or `macos/`) +After the code is generated, you implement the generated native interfaces +in your host project (such as `android/`, `ios/`, or `macos/`) and register them with the Flutter engine. -Finally, call the generated Dart classes from your Flutter app, +Finally, you call the generated Dart classes from your Flutter app, or invoke the generated native APIs to call back into your Dart code. -To modify the API later, update the Pigeon definition file, -regenerate the code, and update your native implementation. +If you need to modify the API, first modify the Pigeon definition file, +regenerate the code, and then update your implementation accordingly. ## Examples -To explore complete implementations of this workflow, +To explore a complete implementation of this flow, including the Pigeon definition file, generated code, and native implementations, refer to the following examples: - [Example of Pigeon with native interop][pigeon-native-example] - [Example of Pigeon with platform channels][pigeon-example] - [`shared_preferences` plugin][shared-preferences-source] - (a production plugin that uses Pigeon with native interop) ## Constraints and limitations -Keep the following constraints in mind when working with Pigeon: +### High-frequency data -### High-frequency raw pointers - -For specialized use cases that require direct manipulation of raw native -pointers or manual C and C++ memory management, +For specialized use cases requiring direct manipulation of raw native pointers +or C and C++ memory management, use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen) instead. ### Inheritance -Pigeon doesn't support complex class inheritance. +Pigeon does not support complex inheritance. Flatten your data models where possible. -### Asynchronous errors on platform channels +### (Pigeon with platform channels only) Async errors -When using Pigeon with platform channels, native implementations of `@async` -methods must return errors through the provided callback. +For `@async` methods, native code must return errors through the provided +callback. ## Resources -To learn about building and migrating plugins with Pigeon, -consult the following resources: - - [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-migration-guide] - [Pigeon native interop guide][pigeon-native-guide] -- [`package:pigeon` on pub.dev][pigeon-pub] +- [Pigeon package on pub.dev][pigeon-pub] - [Pigeon source code][pigeon-source] - [Flutter native interop demos][native-interop-demos] -[Dart isolates]: {{site.dart-site}}/language/isolates [pigeon-pub]: {{site.pub-pkg}}/pigeon [pigeon-source]: {{site.repo.packages}}/tree/main/packages/pigeon [pigeon-example]: {{site.repo.packages}}/tree/main/packages/pigeon/example From 6a8c8c71b979a4e7f3e94f6fce0f0f0f308f5717 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 20 Oct 2026 14:18:11 +0000 Subject: [PATCH 06/16] Refine interop decision tree, table styling, and wording per review --- .../platform-integration/interop/dart-ffi.md | 5 +-- .../platform-integration/interop/ffigen.md | 12 +++--- .../platform-integration/interop/index.md | 43 +++++++++++-------- .../platform-integration/interop/jnigen.md | 23 +++++----- .../platform-integration/interop/pigeon.md | 10 +++-- 5 files changed, 49 insertions(+), 44 deletions(-) diff --git a/sites/docs/src/content/platform-integration/interop/dart-ffi.md b/sites/docs/src/content/platform-integration/interop/dart-ffi.md index 0197ac373b8..b66f07ae8d2 100644 --- a/sites/docs/src/content/platform-integration/interop/dart-ffi.md +++ b/sites/docs/src/content/platform-integration/interop/dart-ffi.md @@ -19,9 +19,8 @@ and enables native memory interop. When you use FFIgen or JNIgen to generate Dart bindings, the generated code does not automatically convert Dart objects like `String` or `List` into native formats. -Because Dart memory is automatically garbage collected -and native C memory is manually managed, -you must use `dart:ffi` to bridge this gap. +Dart memory is automatically garbage collected while native C memory is +manually managed, so you must use `dart:ffi` to bridge this gap. The two most common scenarios when `dart:ffi` is needed are memory allocation and data conversion. diff --git a/sites/docs/src/content/platform-integration/interop/ffigen.md b/sites/docs/src/content/platform-integration/interop/ffigen.md index 1e697138b87..df93f33daf5 100644 --- a/sites/docs/src/content/platform-integration/interop/ffigen.md +++ b/sites/docs/src/content/platform-integration/interop/ffigen.md @@ -44,19 +44,17 @@ Generating bindings with FFIgen follows these steps: 1. Call the generated code from Dart. To start, identify the native header files or APIs you want to wrap. -Then write a configuration script (typically `tool/ffigen.dart` or -`ffigen.yaml`). +Then write a configuration script (typically `tool/ffigen.dart`). This configuration specifies where the header files are located and where to output the generated Dart code. -Next, run the generator tool (such as `dart run tool/ffigen.dart` or -`dart run ffigen`), which parses the headers and generates the corresponding -Dart bindings. +Next, run the configuration script (`dart run tool/ffigen.dart`), +which parses the headers and generates the corresponding Dart bindings. Last, import the generated Dart file into your app and call the functions directly. -Because FFIgen communicates directly using the underlying FFI layer, -refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +FFIgen communicates directly through the underlying FFI layer, +so refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) to learn how to manage native memory lifetimes and execute native calls without blocking the UI thread. diff --git a/sites/docs/src/content/platform-integration/interop/index.md b/sites/docs/src/content/platform-integration/interop/index.md index 5c590416be1..39dd6c21374 100644 --- a/sites/docs/src/content/platform-integration/interop/index.md +++ b/sites/docs/src/content/platform-integration/interop/index.md @@ -15,6 +15,12 @@ or read on to learn about the native interop layers. ## Choose your interop solution +:::important +Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial. +Choose your interop solution carefully based on your initial project +constraints. +::: + ```mermaid flowchart TD Start(["Step 1: What is your primary goal?"]) --> GoalA["Goal A: Platform integration
    (Swift, Kotlin, Java, Obj-C)"] @@ -38,16 +44,18 @@ Follow the steps below to walk through the decision tree in detail: ### Step 1: Determine your primary goal -- **Goal A: Platform integration.** - Communicate with native OS features (Swift, Kotlin, Java, or Objective-C) +**Goal A: Platform integration** +: Communicate with native OS features (Swift, Kotlin, Java, or Objective-C) or expose an API surface. Proceed to [Step 2: Evaluate Pigeon for Flutter plugins](#step-2-evaluate-pigeon-for-flutter-plugins). -- **Goal B: C-ABI wrapping.** - Wrap an existing native library (C, C++, Rust, or Go). + +**Goal B: C-ABI wrapping** +: Wrap an existing native library (C, C++, Rust, or Go). Use [FFIgen](/platform-integration/interop/ffigen) (or [Flutter Rust Bridge][frb] for Rust). -- **Goal C: Pure Dart package.** - Build a Dart CLI tool without Flutter framework dependencies. + +**Goal C: Pure Dart package** +: Build a Dart CLI tool without Flutter framework dependencies. Use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen). @@ -57,12 +65,6 @@ If you are building a Flutter plugin, first verify whether [Pigeon](/platform-integration/interop/pigeon) satisfies your technical constraints. -:::important -Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial. -Choose your interop solution carefully based on your initial project -constraints. -::: - Do any of the following requirements apply to your feature? - Your app processes high-frequency raw data (for example, video processing). @@ -72,13 +74,16 @@ Do any of the following requirements apply to your feature? guardrails. - Your target language is C, C++, Rust, or Go. -- **No (most common): Use [Pigeon](/platform-integration/interop/pigeon).** - This is the default, recommended path. +Based on whether any of the preceding requirements apply, +choose your interop tool: + +**No (most common) — Use [Pigeon](/platform-integration/interop/pigeon)** +: This is the default, recommended path. It provides necessary guardrails, creates easily digestible interface mappings, and is highly efficient for most API surface areas. -- **Yes: Use [FFIgen](/platform-integration/interop/ffigen) or - [JNIgen](/platform-integration/interop/jnigen).** - You need the flexibility, complexity, and performance of direct interop for + +**Yes — Use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen)** +: You need the flexibility, complexity, and performance of direct interop for larger surface areas. :::note @@ -110,7 +115,7 @@ refer to [Using packages](/packages-and-plugins/using-packages). - **FFI package.** A package that compiles and bundles native C or C++ code using Dart build hooks and [`dart:ffi`](/platform-integration/interop/dart-ffi). - This template also includes an `ffigen.yaml` configuration file to regenerate + This template also includes an FFIgen configuration to regenerate Dart bindings (`flutter create --template=package_ffi`). For more details, refer to [Developing FFI packages](/packages-and-plugins/developing-packages#ffi). @@ -148,6 +153,8 @@ languages: | **Objective-C** | [FFIgen](/platform-integration/interop/ffigen) | Supported through Swift | | **Swift without Obj-C compatibility annotations** | [`package:swiftgen`][swiftgen-pub] (experimental) | Supported (recommended) | +{:.table .table-striped} + ## Understand interop layers Dart structures native interop into three distinct layers: diff --git a/sites/docs/src/content/platform-integration/interop/jnigen.md b/sites/docs/src/content/platform-integration/interop/jnigen.md index b4d3ce559dd..92287949cc1 100644 --- a/sites/docs/src/content/platform-integration/interop/jnigen.md +++ b/sites/docs/src/content/platform-integration/interop/jnigen.md @@ -17,8 +17,8 @@ Dart code. Use JNIgen when you need to call Java or Kotlin libraries, wrap compiled JAR or AAR files, or interact directly with Android from your Dart code. -Because JNIgen uses direct JNI bindings through `dart:ffi`, -it is also the recommended tool when you require high-performance, +JNIgen uses direct JNI bindings through `dart:ffi`, +making it the recommended tool when you require high-performance, low-latency interop, or need to pass massive amounts of raw data between Dart and the JVM. @@ -42,21 +42,19 @@ For a complete list of configuration options, consult the To start, identify the native Java classes, packages, or compiled JAR/AAR files you want to wrap. -Then write a configuration script (typically `tool/jnigen.dart` or -`jnigen.yaml`). +Then write a configuration script (typically `tool/jnigen.dart`). This configuration specifies where the native files are located, what packages to include, and where to output the generated Dart code. -Next, run the generator tool (such as `dart run tool/jnigen.dart` or -`dart run jnigen --config jnigen.yaml`), +Next, run the configuration script (`dart run tool/jnigen.dart`), which parses the classes and generates the corresponding Dart bindings. Last, import the generated Dart file into your app and call the functions directly. -Because JNIgen communicates directly using the underlying FFI layer, -refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) to learn -how to manage native memory lifetimes and execute native calls without blocking -the UI thread. +JNIgen communicates directly through the underlying FFI layer, +so refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +to learn how to manage native memory lifetimes and execute native calls +without blocking the UI thread. :::note If the native API changes, you need to update your configuration script and @@ -97,9 +95,8 @@ when implementing interfaces. ### JVM runtime type checks -JNI wrappers are Dart extension types, and they compile down to `JObject` -at runtime. -Because of this, Dart's native `is` and `as` keywords do not check the Java +JNI wrappers are Dart extension types that compile down to `JObject` +at runtime, so Dart's native `is` and `as` keywords do not check the Java runtime type. Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/platform-integration/interop/pigeon.md index a20f7762976..1f695d94377 100644 --- a/sites/docs/src/content/platform-integration/interop/pigeon.md +++ b/sites/docs/src/content/platform-integration/interop/pigeon.md @@ -26,8 +26,9 @@ or [JNIgen](/platform-integration/interop/jnigen) instead. ### Pigeon with native interop -Pigeon with native interop (direct FFI and JNI) generates direct native bindings -using FFI on iOS and macOS, and JNI on Android. +Pigeon with native interop uses [FFIgen](/platform-integration/interop/ffigen) +and [JNIgen](/platform-integration/interop/jnigen) under the hood to generate +direct native bindings (using FFI on iOS and macOS, and JNI on Android). Use this option if your use case requires any of the following: @@ -72,7 +73,8 @@ refer to the [native interop guide][pigeon-native-guide]. ::: After the code is generated, you implement the generated native interfaces -in your host project (such as `android/`, `ios/`, or `macos/`) +in the platform-specific directories of your Flutter project +(such as `android/`, `ios/`, or `macos/`) and register them with the Flutter engine. Finally, you call the generated Dart classes from your Flutter app, or invoke the generated native APIs to call back into your Dart code. @@ -92,6 +94,8 @@ and native implementations, refer to the following examples: ## Constraints and limitations +Keep the following constraints and limitations in mind when working with Pigeon: + ### High-frequency data For specialized use cases requiring direct manipulation of raw native pointers From 2d4fa00b326ada5c74d3a2ed1320174835c5e1a9 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 20 Oct 2026 15:02:29 +0000 Subject: [PATCH 07/16] Move Native interop section to top-level /interop path --- sites/docs/firebase.json | 2 +- .../interop/bind-native-code.md | 0 .../interop/dart-ffi.md | 6 ++-- .../interop/ffigen.md | 4 +-- .../interop/index.md | 36 +++++++++---------- .../interop/jnigen.md | 4 +-- .../interop/pigeon.md | 12 +++---- .../developing-packages.md | 2 +- .../platform-integration/android/setup.md | 2 +- .../src/content/platform-integration/index.md | 8 ++--- .../content/platform-integration/ios/setup.md | 2 +- .../platform-integration/legacy-ffi-plugin.md | 2 +- .../platform-integration/linux/building.md | 2 +- .../platform-integration/macos/setup.md | 2 +- .../platform-integration/platform-channels.md | 12 +++---- .../platform-integration/windows/building.md | 2 +- sites/docs/src/data/sidenav/default.yml | 16 ++++----- 17 files changed, 57 insertions(+), 57 deletions(-) rename sites/docs/src/content/{platform-integration => }/interop/bind-native-code.md (100%) rename sites/docs/src/content/{platform-integration => }/interop/dart-ffi.md (92%) rename sites/docs/src/content/{platform-integration => }/interop/ffigen.md (96%) rename sites/docs/src/content/{platform-integration => }/interop/index.md (82%) rename sites/docs/src/content/{platform-integration => }/interop/jnigen.md (96%) rename sites/docs/src/content/{platform-integration => }/interop/pigeon.md (92%) diff --git a/sites/docs/firebase.json b/sites/docs/firebase.json index 523259afa76..9d499832653 100644 --- a/sites/docs/firebase.json +++ b/sites/docs/firebase.json @@ -196,7 +196,7 @@ { "source": "/platform-integration/android/install-android", "destination": "/platform-integration/android/setup", "type": 301 }, { "source": "/platform-integration/android/install-android/:rest*", "destination": "/platform-integration/android/setup", "type": 301 }, { "source": "/platform-integration/android/splash-screen-migration", "destination": "/release/breaking-changes/splash-screen-migration", "type": 301 }, - { "source": "/platform-integration/bind-native-code", "destination": "/platform-integration/interop/bind-native-code", "type": 301 }, + { "source": "/platform-integration/bind-native-code", "destination": "/interop/bind-native-code", "type": 301 }, { "source": "/platform-integration/c-interop", "destination": "/platform-integration/legacy-ffi-plugin", "type": 301 }, { "source": "/platform-integration/ios-app-clip", "destination": "/platform-integration/ios/ios-app-clip", "type": 301 }, { "source": "/platform-integration/ios/c-interop", "destination": "/platform-integration/legacy-ffi-plugin", "type": 301 }, diff --git a/sites/docs/src/content/platform-integration/interop/bind-native-code.md b/sites/docs/src/content/interop/bind-native-code.md similarity index 100% rename from sites/docs/src/content/platform-integration/interop/bind-native-code.md rename to sites/docs/src/content/interop/bind-native-code.md diff --git a/sites/docs/src/content/platform-integration/interop/dart-ffi.md b/sites/docs/src/content/interop/dart-ffi.md similarity index 92% rename from sites/docs/src/content/platform-integration/interop/dart-ffi.md rename to sites/docs/src/content/interop/dart-ffi.md index b66f07ae8d2..577080bcd20 100644 --- a/sites/docs/src/content/platform-integration/interop/dart-ffi.md +++ b/sites/docs/src/content/interop/dart-ffi.md @@ -8,9 +8,9 @@ description: >- The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library that provides the low-level APIs to call C-ABI functions from Dart. -Rather than a code generation tool like [Pigeon](/platform-integration/interop/pigeon), -[FFIgen](/platform-integration/interop/ffigen), -or [JNIgen](/platform-integration/interop/jnigen), +Rather than a code generation tool like [Pigeon](/interop/pigeon), +[FFIgen](/interop/ffigen), +or [JNIgen](/interop/jnigen), `dart:ffi` is the underlying technology that powers these tools and enables native memory interop. diff --git a/sites/docs/src/content/platform-integration/interop/ffigen.md b/sites/docs/src/content/interop/ffigen.md similarity index 96% rename from sites/docs/src/content/platform-integration/interop/ffigen.md rename to sites/docs/src/content/interop/ffigen.md index df93f33daf5..13d6843ea71 100644 --- a/sites/docs/src/content/platform-integration/interop/ffigen.md +++ b/sites/docs/src/content/interop/ffigen.md @@ -31,7 +31,7 @@ FFIgen cannot parse C++ classes or templates directly. ::: For standard, cross-platform plugin development where you don't have -critical data transfers, use [Pigeon](/platform-integration/interop/pigeon) +critical data transfers, use [Pigeon](/interop/pigeon) instead. ## Developer flow @@ -54,7 +54,7 @@ which parses the headers and generates the corresponding Dart bindings. Last, import the generated Dart file into your app and call the functions directly. FFIgen communicates directly through the underlying FFI layer, -so refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +so refer to the [`dart:ffi` guide](/interop/dart-ffi) to learn how to manage native memory lifetimes and execute native calls without blocking the UI thread. diff --git a/sites/docs/src/content/platform-integration/interop/index.md b/sites/docs/src/content/interop/index.md similarity index 82% rename from sites/docs/src/content/platform-integration/interop/index.md rename to sites/docs/src/content/interop/index.md index 39dd6c21374..83779d44906 100644 --- a/sites/docs/src/content/platform-integration/interop/index.md +++ b/sites/docs/src/content/interop/index.md @@ -51,18 +51,18 @@ Follow the steps below to walk through the decision tree in detail: **Goal B: C-ABI wrapping** : Wrap an existing native library (C, C++, Rust, or Go). - Use [FFIgen](/platform-integration/interop/ffigen) + Use [FFIgen](/interop/ffigen) (or [Flutter Rust Bridge][frb] for Rust). **Goal C: Pure Dart package** : Build a Dart CLI tool without Flutter framework dependencies. - Use [FFIgen](/platform-integration/interop/ffigen) or - [JNIgen](/platform-integration/interop/jnigen). + Use [FFIgen](/interop/ffigen) or + [JNIgen](/interop/jnigen). ### Step 2: Evaluate Pigeon for Flutter plugins If you are building a Flutter plugin, -first verify whether [Pigeon](/platform-integration/interop/pigeon) satisfies +first verify whether [Pigeon](/interop/pigeon) satisfies your technical constraints. Do any of the following requirements apply to your feature? @@ -77,12 +77,12 @@ Do any of the following requirements apply to your feature? Based on whether any of the preceding requirements apply, choose your interop tool: -**No (most common) — Use [Pigeon](/platform-integration/interop/pigeon)** +**No (most common) — Use [Pigeon](/interop/pigeon)** : This is the default, recommended path. It provides necessary guardrails, creates easily digestible interface mappings, and is highly efficient for most API surface areas. -**Yes — Use [FFIgen](/platform-integration/interop/ffigen) or [JNIgen](/platform-integration/interop/jnigen)** +**Yes — Use [FFIgen](/interop/ffigen) or [JNIgen](/interop/jnigen)** : You need the flexibility, complexity, and performance of direct interop for larger surface areas. @@ -114,7 +114,7 @@ refer to [Using packages](/packages-and-plugins/using-packages). [Developing plugin packages](/packages-and-plugins/developing-packages#plugin). - **FFI package.** A package that compiles and bundles native C or C++ code using Dart build - hooks and [`dart:ffi`](/platform-integration/interop/dart-ffi). + hooks and [`dart:ffi`](/interop/dart-ffi). This template also includes an FFIgen configuration to regenerate Dart bindings (`flutter create --template=package_ffi`). For more details, refer to @@ -141,16 +141,16 @@ languages: | Target language | Direct interop | Pigeon | | --- | --- | --- | -| **C** | [FFIgen](/platform-integration/interop/ffigen) | Not recommended. Supported with platform channels, but not maintained by the Flutter team. | -| **C++ with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **C** | [FFIgen](/interop/ffigen) | Not recommended. Supported with platform channels, but not maintained by the Flutter team. | +| **C++ with `extern "C"`** | [FFIgen](/interop/ffigen) | (Refer to C) | | **C++ without `extern "C"`** | FFIgen (not yet supported) | Supported with platform channels, but not maintained by the Flutter team. | -| **Rust with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **Rust with `extern "C"`** | [FFIgen](/interop/ffigen) | (Refer to C) | | **Rust without `extern "C"`** | [Flutter Rust Bridge][frb] | Not supported | -| **Go with `extern "C"`** | [FFIgen](/platform-integration/interop/ffigen) | (Refer to C) | +| **Go with `extern "C"`** | [FFIgen](/interop/ffigen) | (Refer to C) | | **Go without `extern "C"`** | Not supported | Not supported | -| **Java** | [JNIgen](/platform-integration/interop/jnigen) | Supported through Kotlin | -| **Kotlin** | [JNIgen](/platform-integration/interop/jnigen) | Supported (recommended) | -| **Objective-C** | [FFIgen](/platform-integration/interop/ffigen) | Supported through Swift | +| **Java** | [JNIgen](/interop/jnigen) | Supported through Kotlin | +| **Kotlin** | [JNIgen](/interop/jnigen) | Supported (recommended) | +| **Objective-C** | [FFIgen](/interop/ffigen) | Supported through Swift | | **Swift without Obj-C compatibility annotations** | [`package:swiftgen`][swiftgen-pub] (experimental) | Supported (recommended) | {:.table .table-striped} @@ -166,9 +166,9 @@ consult the [native interop layers guide][interop-layers]. ### Layer 1: Low-level bindings Layer 1 consists of thin, high-performance, and unopinionated wrappers around -native code (for example, [`dart:ffi`](/platform-integration/interop/dart-ffi), -`dart:js_interop`, [FFIgen](/platform-integration/interop/ffigen), -and [JNIgen](/platform-integration/interop/jnigen)). +native code (for example, [`dart:ffi`](/interop/dart-ffi), +`dart:js_interop`, [FFIgen](/interop/ffigen), +and [JNIgen](/interop/jnigen)). These APIs mirror native signatures closely, but require manual memory and threading management. @@ -176,7 +176,7 @@ but require manual memory and threading management. Layer 2 consists of idiomatic Dart APIs that wrap the low-level layer to hide platform-specific complexities (for example, -[Pigeon](/platform-integration/interop/pigeon) or the +[Pigeon](/interop/pigeon) or the [`sqlite3` package][sqlite3-pub]). These manage concurrency, memory lifetimes, and data conversions automatically. diff --git a/sites/docs/src/content/platform-integration/interop/jnigen.md b/sites/docs/src/content/interop/jnigen.md similarity index 96% rename from sites/docs/src/content/platform-integration/interop/jnigen.md rename to sites/docs/src/content/interop/jnigen.md index 92287949cc1..f12efb7e32c 100644 --- a/sites/docs/src/content/platform-integration/interop/jnigen.md +++ b/sites/docs/src/content/interop/jnigen.md @@ -23,7 +23,7 @@ low-latency interop, or need to pass massive amounts of raw data between Dart and the JVM. For standard, cross-platform plugin development where you don't have -critical data transfers, use [Pigeon](/platform-integration/interop/pigeon) +critical data transfers, use [Pigeon](/interop/pigeon) instead. ## Developer flow @@ -52,7 +52,7 @@ which parses the classes and generates the corresponding Dart bindings. Last, import the generated Dart file into your app and call the functions directly. JNIgen communicates directly through the underlying FFI layer, -so refer to the [`dart:ffi` guide](/platform-integration/interop/dart-ffi) +so refer to the [`dart:ffi` guide](/interop/dart-ffi) to learn how to manage native memory lifetimes and execute native calls without blocking the UI thread. diff --git a/sites/docs/src/content/platform-integration/interop/pigeon.md b/sites/docs/src/content/interop/pigeon.md similarity index 92% rename from sites/docs/src/content/platform-integration/interop/pigeon.md rename to sites/docs/src/content/interop/pigeon.md index 1f695d94377..0b54de8d58a 100644 --- a/sites/docs/src/content/platform-integration/interop/pigeon.md +++ b/sites/docs/src/content/interop/pigeon.md @@ -20,14 +20,14 @@ native interop (direct FFI and JNI) or platform channels. :::note For low-level integrations that require direct access to native memory -or C and C++ libraries, use [FFIgen](/platform-integration/interop/ffigen) -or [JNIgen](/platform-integration/interop/jnigen) instead. +or C and C++ libraries, use [FFIgen](/interop/ffigen) +or [JNIgen](/interop/jnigen) instead. ::: ### Pigeon with native interop -Pigeon with native interop uses [FFIgen](/platform-integration/interop/ffigen) -and [JNIgen](/platform-integration/interop/jnigen) under the hood to generate +Pigeon with native interop uses [FFIgen](/interop/ffigen) +and [JNIgen](/interop/jnigen) under the hood to generate direct native bindings (using FFI on iOS and macOS, and JNI on Android). Use this option if your use case requires any of the following: @@ -100,8 +100,8 @@ Keep the following constraints and limitations in mind when working with Pigeon: For specialized use cases requiring direct manipulation of raw native pointers or C and C++ memory management, -use [FFIgen](/platform-integration/interop/ffigen) or -[JNIgen](/platform-integration/interop/jnigen) instead. +use [FFIgen](/interop/ffigen) or +[JNIgen](/interop/jnigen) instead. ### Inheritance diff --git a/sites/docs/src/content/packages-and-plugins/developing-packages.md b/sites/docs/src/content/packages-and-plugins/developing-packages.md index 21b3099524a..2ce8c6757fd 100644 --- a/sites/docs/src/content/packages-and-plugins/developing-packages.md +++ b/sites/docs/src/content/packages-and-plugins/developing-packages.md @@ -1036,7 +1036,7 @@ For additional details on `.podspec` files, see the All web dependencies are handled by the `pubspec.yaml` file, like any other Dart package. -[bind-native]: /platform-integration/interop/bind-native-code +[bind-native]: /interop/bind-native-code [CocoaPods Documentation]: https://guides.cocoapods.org/syntax/podspec.html [Dart library package]: {{site.dart-site}}/guides/libraries/create-library-packages [`device_info_plus`]: {{site.pub-api}}/device_info_plus diff --git a/sites/docs/src/content/platform-integration/android/setup.md b/sites/docs/src/content/platform-integration/android/setup.md index 343189bb048..a1e79c7b637 100644 --- a/sites/docs/src/content/platform-integration/android/setup.md +++ b/sites/docs/src/content/platform-integration/android/setup.md @@ -377,7 +377,7 @@ or begin improving integration with Android. Build and deploy to Android
  • - Bind to native Android code + Bind to native Android code
  • Add a splash screen diff --git a/sites/docs/src/content/platform-integration/index.md b/sites/docs/src/content/platform-integration/index.md index ac2407a77c5..ebae7183af6 100644 --- a/sites/docs/src/content/platform-integration/index.md +++ b/sites/docs/src/content/platform-integration/index.md @@ -32,7 +32,7 @@ also consider building your UI with [adaptive and responsive design][] in mind. [supported platforms]: /reference/supported-platforms [Flutter plugins]: /packages-and-plugins/using-packages -[Native interop]: /platform-integration/interop +[Native interop]: /interop [write platform-specific code]: /platform-integration/platform-channels [create your own plugin]: /packages-and-plugins/developing-packages [adaptive and responsive design]: /ui/adaptive-responsive/ @@ -142,7 +142,7 @@ Learn how to add custom integrations with Android to your Flutter app.

    Learn how the latest Android APIs in your app from Dart.

    - +
    Bind to native code
    @@ -197,7 +197,7 @@ Learn how to add custom integrations with iOS to your Flutter app.

    Learn about plugins that support functionality from native iOS frameworks.

    - +
    Bind to native code
    @@ -318,7 +318,7 @@ Learn how to add custom integrations with Windows to your Flutter app. Learn how to add custom integrations with macOS to your Flutter app.
  • - Bind to native iOS code + Bind to native iOS code
  • Leverage system frameworks diff --git a/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md b/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md index e0c7e7bdc5c..31855f9b125 100644 --- a/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md +++ b/sites/docs/src/content/platform-integration/legacy-ffi-plugin.md @@ -25,7 +25,7 @@ _FFI_ stands for [_foreign function interface._][FFI] Other terms for similar functionality include _native interface_ and _language bindings._ -[build hooks]: /platform-integration/interop/bind-native-code +[build hooks]: /interop/bind-native-code [`dart:ffi`]: {{site.dart.api}}/dart-ffi/dart-ffi-library.html [FFI]: https://en.wikipedia.org/wiki/Foreign_function_interface diff --git a/sites/docs/src/content/platform-integration/linux/building.md b/sites/docs/src/content/platform-integration/linux/building.md index bb997e0702f..7b8ee8188ad 100644 --- a/sites/docs/src/content/platform-integration/linux/building.md +++ b/sites/docs/src/content/platform-integration/linux/building.md @@ -40,7 +40,7 @@ including common packages such as [`url_launcher`], [`shared_preferences`], [`file_selector`], and [`path_provider`]. [C interop using `dart:ffi`]: {{site.dart-site}}/guides/libraries/c-interop -[Bind to native code using FFI]: /platform-integration/interop/bind-native-code +[Bind to native code using FFI]: /interop/bind-native-code [Canonical]: {{site.pub}}/publishers/canonical.com/packages [support-linux]: {{site.pub}}/packages?q=platform%3Alinux [`url_launcher`]: {{site.pub-pkg}}/url_launcher diff --git a/sites/docs/src/content/platform-integration/macos/setup.md b/sites/docs/src/content/platform-integration/macos/setup.md index 2562690644f..77946c6fe65 100644 --- a/sites/docs/src/content/platform-integration/macos/setup.md +++ b/sites/docs/src/content/platform-integration/macos/setup.md @@ -169,7 +169,7 @@ or begin expanding integration with macOS. Build and deploy to macOS
  • - Bind to native macOS code + Bind to native macOS code
  • Embed native macOS views diff --git a/sites/docs/src/content/platform-integration/platform-channels.md b/sites/docs/src/content/platform-integration/platform-channels.md index bb86fc8ee1f..0fe4db1e032 100644 --- a/sites/docs/src/content/platform-integration/platform-channels.md +++ b/sites/docs/src/content/platform-integration/platform-channels.md @@ -7,16 +7,16 @@ description: Learn how to write platform-specific code using Flutter's platform :::note -Refer to the [Native interop overview](/platform-integration/interop) +Refer to the [Native interop overview](/interop) for recommended approaches to calling native platform code. ::: Platform channels pass messages between Flutter and your host platform (Android, iOS, macOS, Windows, or Linux). -While [Pigeon](/platform-integration/interop/pigeon), -[FFIgen](/platform-integration/interop/ffigen), and -[JNIgen](/platform-integration/interop/jnigen) are recommended for new projects, +While [Pigeon](/interop/pigeon), +[FFIgen](/interop/ffigen), and +[JNIgen](/interop/jnigen) are recommended for new projects, platform channels remain available for low-level message passing and maintaining existing codebases. @@ -30,7 +30,7 @@ Common ways to do this include: For details, refer to [Call platform-specific code using platform channels](#example). -- Use [Pigeon](/platform-integration/interop/pigeon) to generate type-safe +- Use [Pigeon](/interop/pigeon) to generate type-safe platform-specific bindings. Flutter supports the following platforms and @@ -1145,7 +1145,7 @@ Pigeon is the recommended default tool for generating type-safe bindings between Dart and host platforms. To learn how to use Pigeon, define API contracts, and choose between native interop and platform channels, -refer to the dedicated [Pigeon guide](/platform-integration/interop/pigeon). +refer to the dedicated [Pigeon guide](/interop/pigeon). ## Channels and platform threading diff --git a/sites/docs/src/content/platform-integration/windows/building.md b/sites/docs/src/content/platform-integration/windows/building.md index b79072ecf0f..e2d53a56f23 100644 --- a/sites/docs/src/content/platform-integration/windows/building.md +++ b/sites/docs/src/content/platform-integration/windows/building.md @@ -47,7 +47,7 @@ More generally, many other [packages support Windows], including common packages such as [`url_launcher`], [`shared_preferences`], [`file_selector`], and [`path_provider`]. [C interop using `dart:ffi`]: {{site.dart-site}}/guides/libraries/c-interop -[Bind to native code using FFI]: /platform-integration/interop/bind-native-code +[Bind to native code using FFI]: /interop/bind-native-code [win32 package]: {{site.pub}}/packages/win32 [Windows registry]: {{site.pub}}/packages/win32_registry [gamepad support]: {{site.pub}}/packages/win32_gamepad diff --git a/sites/docs/src/data/sidenav/default.yml b/sites/docs/src/data/sidenav/default.yml index 0652d095d6f..e869b3ae068 100644 --- a/sites/docs/src/data/sidenav/default.yml +++ b/sites/docs/src/data/sidenav/default.yml @@ -362,24 +362,24 @@ permalink: /app-architecture/design-patterns - title: Native interop - permalink: /platform-integration/interop + permalink: /interop icon: alt_route children: - title: Overview - permalink: /platform-integration/interop + permalink: /interop - title: Pigeon - permalink: /platform-integration/interop/pigeon + permalink: /interop/pigeon - title: FFIgen - permalink: /platform-integration/interop/ffigen + permalink: /interop/ffigen children: - title: Overview - permalink: /platform-integration/interop/ffigen + permalink: /interop/ffigen - title: "dart:ffi" - permalink: /platform-integration/interop/dart-ffi + permalink: /interop/dart-ffi - title: Bind to native code - permalink: /platform-integration/interop/bind-native-code + permalink: /interop/bind-native-code - title: JNIgen - permalink: /platform-integration/interop/jnigen + permalink: /interop/jnigen - title: Platform channels permalink: /platform-integration/platform-channels From 8fe1d4a475fc550c504d770d5c6abe4b20114f97 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Tue, 20 Oct 2026 15:13:04 +0000 Subject: [PATCH 08/16] Refine Step 2 migration note, table intro, and Resources intros --- sites/docs/src/content/interop/ffigen.md | 3 +++ sites/docs/src/content/interop/index.md | 15 +++++++-------- sites/docs/src/content/interop/jnigen.md | 3 +++ sites/docs/src/content/interop/pigeon.md | 3 +++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/sites/docs/src/content/interop/ffigen.md b/sites/docs/src/content/interop/ffigen.md index 13d6843ea71..84b36e6b7fd 100644 --- a/sites/docs/src/content/interop/ffigen.md +++ b/sites/docs/src/content/interop/ffigen.md @@ -128,6 +128,9 @@ package. ## Resources +To learn about generating C, Objective-C, and Swift bindings with FFIgen, +refer to the following resources: + - [FFIgen package on pub.dev][ffigen-pub] - [`objective_c` package on pub.dev][objc-pub] - [Official FFIgen examples on GitHub][ffigen-examples] diff --git a/sites/docs/src/content/interop/index.md b/sites/docs/src/content/interop/index.md index 83779d44906..068c4dc5167 100644 --- a/sites/docs/src/content/interop/index.md +++ b/sites/docs/src/content/interop/index.md @@ -15,12 +15,6 @@ or read on to learn about the native interop layers. ## Choose your interop solution -:::important -Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial. -Choose your interop solution carefully based on your initial project -constraints. -::: - ```mermaid flowchart TD Start(["Step 1: What is your primary goal?"]) --> GoalA["Goal A: Platform integration
    (Swift, Kotlin, Java, Obj-C)"] @@ -65,6 +59,12 @@ If you are building a Flutter plugin, first verify whether [Pigeon](/interop/pigeon) satisfies your technical constraints. +:::important +Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial +because the glue code lives in different languages. +Evaluate your plugin's constraints carefully before choosing between them. +::: + Do any of the following requirements apply to your feature? - Your app processes high-frequency raw data (for example, video processing). @@ -131,8 +131,7 @@ refer to [Developing packages and plugins](/packages-and-plugins/developing-pack ## Tool comparison -The following table compares the two interop approaches across target -languages: +The following table compares two interop solutions: - **Direct interop:** Lower-level direct bindings with higher performance for large data; requires manual memory and threading management. diff --git a/sites/docs/src/content/interop/jnigen.md b/sites/docs/src/content/interop/jnigen.md index f12efb7e32c..838e58fbe22 100644 --- a/sites/docs/src/content/interop/jnigen.md +++ b/sites/docs/src/content/interop/jnigen.md @@ -109,6 +109,9 @@ and help trace leaks. ## Resources +To learn about generating Java and Kotlin bindings with JNIgen, +refer to the following resources: + - [JNIgen package on pub.dev][jnigen-pub] - [JNI package on pub.dev][jni-pub] - [Official JNIgen examples on GitHub][jnigen-examples] diff --git a/sites/docs/src/content/interop/pigeon.md b/sites/docs/src/content/interop/pigeon.md index 0b54de8d58a..a9f429c886e 100644 --- a/sites/docs/src/content/interop/pigeon.md +++ b/sites/docs/src/content/interop/pigeon.md @@ -115,6 +115,9 @@ callback. ## Resources +To learn about building and migrating plugins with Pigeon, +refer to the following resources: + - [Migrating from Pigeon with platform channels to Pigeon with native interop][pigeon-migration-guide] - [Pigeon native interop guide][pigeon-native-guide] - [Pigeon package on pub.dev][pigeon-pub] From 5a26d72a9ae7b8cd14e7646d13c6f4e030de7260 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Thu, 29 Oct 2026 01:28:15 +0000 Subject: [PATCH 09/16] Address PR review feedback from Shams and Tarrin --- .../src/content/interop/bind-native-code.md | 4 +-- sites/docs/src/content/interop/dart-ffi.md | 18 +++++------ sites/docs/src/content/interop/ffigen.md | 32 +++++++++++-------- sites/docs/src/content/interop/index.md | 21 ++++++------ sites/docs/src/content/interop/jnigen.md | 15 +++++---- sites/docs/src/content/interop/pigeon.md | 26 ++++++++------- .../platform-integration/platform-channels.md | 1 - 7 files changed, 63 insertions(+), 54 deletions(-) diff --git a/sites/docs/src/content/interop/bind-native-code.md b/sites/docs/src/content/interop/bind-native-code.md index a5cec27b39a..5d19bf4c2e4 100644 --- a/sites/docs/src/content/interop/bind-native-code.md +++ b/sites/docs/src/content/interop/bind-native-code.md @@ -114,9 +114,9 @@ For Windows, you often use `DynamicLoadingSystem()` and provide the name of the DLL. :::note -For desktop-specific guidance on calling system APIs—including wrapper +For desktop-specific guidance on calling system APIs—including wrapper packages like [`package:win32`]({{site.pub-pkg}}/win32) and -[Canonical's Linux packages]({{site.pub}}/publishers/canonical.com/packages)—refer to +[Canonical's Linux packages]({{site.pub}}/publishers/canonical.com/packages)—refer to [Integrating with Windows](/platform-integration/windows/building#integrating-with-windows) and [Integrate with Linux](/platform-integration/linux/building#integrate-with-linux). ::: diff --git a/sites/docs/src/content/interop/dart-ffi.md b/sites/docs/src/content/interop/dart-ffi.md index 577080bcd20..7518960e775 100644 --- a/sites/docs/src/content/interop/dart-ffi.md +++ b/sites/docs/src/content/interop/dart-ffi.md @@ -8,7 +8,7 @@ description: >- The `dart:ffi` (Foreign Function Interface) library is a core Dart SDK library that provides the low-level APIs to call C-ABI functions from Dart. -Rather than a code generation tool like [Pigeon](/interop/pigeon), +Rather than code generation tools like [Pigeon](/interop/pigeon), [FFIgen](/interop/ffigen), or [JNIgen](/interop/jnigen), `dart:ffi` is the underlying technology that powers these tools @@ -17,24 +17,24 @@ and enables native memory interop. ## When to use `dart:ffi` When you use FFIgen or JNIgen to generate Dart bindings, -the generated code does not automatically convert Dart objects +the generated code doesn't automatically convert Dart objects like `String` or `List` into native formats. Dart memory is automatically garbage collected while native C memory is -manually managed, so you must use `dart:ffi` to bridge this gap. +manually managed, so use `dart:ffi` to bridge this gap. The two most common scenarios when `dart:ffi` is needed are memory allocation and data conversion. -With memory allocation, you use `dart:ffi` to allocate space on the system heap -for native arguments, and you must manually free that memory when done. -This helps avoid memory leaks. +With memory allocation, use `dart:ffi` to allocate space on the system heap +for native arguments, and manually free that memory when done +to prevent memory leaks. -With data conversion, you use `dart:ffi` to convert Dart data types +With data conversion, use `dart:ffi` to convert Dart data types so they can be read by native code. ## Core tools: `dart:ffi` and `package:ffi` -To write interop code, you use two libraries: +To write interop code, use two libraries: - **`dart:ffi`:** Built into the core Dart SDK. It provides native representation types and lookup signatures. @@ -43,7 +43,7 @@ To write interop code, you use two libraries: ## Concurrency in Flutter -FFI calls are synchronous and run on the thread they are called on. +FFI calls are synchronous and run on the thread on which they're called. If you invoke a heavy or blocking C function on your Flutter app's main UI thread, your app freezes or drops frames. To prevent this, execute blocking FFI calls inside a background Dart isolate diff --git a/sites/docs/src/content/interop/ffigen.md b/sites/docs/src/content/interop/ffigen.md index 84b36e6b7fd..4c3c6999b4a 100644 --- a/sites/docs/src/content/interop/ffigen.md +++ b/sites/docs/src/content/interop/ffigen.md @@ -10,11 +10,11 @@ description: >- FFIgen (the Foreign Function Interface generator) is a code generator tool that creates type-safe Dart bindings for C libraries and Apple frameworks (Objective-C and Swift). -This allows you to call Apple platform APIs (iOS and macOS) directly +This allows calling Apple platform APIs (iOS and macOS) directly and synchronously from your Dart code. :::note -Direct Swift interop is in experimental support. +Direct Swift interop is in experimental support and is likely to change. ::: ## When to use FFIgen @@ -25,9 +25,9 @@ or interact directly with Objective-C and Swift frameworks on iOS and macOS. :::note FFIgen parses C and Objective-C headers. -To wrap C++ libraries, you must expose a C-compatible interface +To wrap C++ libraries, expose a C-compatible interface around your C++ code. -FFIgen cannot parse C++ classes or templates directly. +FFIgen can't parse C++ classes or templates directly. ::: For standard, cross-platform plugin development where you don't have @@ -36,7 +36,7 @@ instead. ## Developer flow -Generating bindings with FFIgen follows these steps: +Generate bindings with FFIgen using the following steps: 1. Reference the native headers. 1. Write the FFIgen configuration script. @@ -59,7 +59,7 @@ to learn how to manage native memory lifetimes and execute native calls without blocking the UI thread. :::note -If the native API changes, you need to update your configuration script +If the native API changes, update your configuration script and regenerate the bindings. ::: @@ -69,7 +69,7 @@ To explore a complete implementation of this flow, including how to configure the generator, refer to the [official FFIgen examples][ffigen-examples]. -The Add example highlights the general flow with a toy native API +The `Add` example highlights the general flow with a toy native API (one function in a single header file). The `code_assets` examples highlight real-world examples like @@ -88,7 +88,7 @@ to prevent runtime crashes. ### OS differences Dart has no equivalent of C macros (like `#if TARGET_OS_IPHONE`), -so you must handle minor API variations using runtime `Platform.isIOS` checks. +so handle minor API variations using runtime `Platform.isIOS` checks. Some Objective-C and Swift-specific pitfalls when working with FFIgen: @@ -96,14 +96,16 @@ Some Objective-C and Swift-specific pitfalls when working with FFIgen: Objective-C methods are dynamically resolved. FFIgen automatically maps method selectors to Dart signatures, -but you should verify selectors by reading the bindings -if you are using heavily overloaded native methods. +but verify selectors by reading the bindings +if you're using heavily overloaded native methods. ### Objective-C memory management Dart uses garbage collection while Objective-C uses reference counting. Passing Dart closures that close over native wrapper objects can easily create reference cycles and leak memory. +To break these reference cycles, wrap the captured object in a `WeakReference`, +as shown in the [Objective-C memory management guide][objc-gc-doc]. ### Objective-C runtime checks @@ -116,15 +118,16 @@ Use `.isA()` and `.as()` instead. UI-related Objective-C calls must run on the iOS main platform thread. Also, some types of callbacks can crash your app -if they are executed on the wrong thread. +if they're executed on the wrong thread. ### Swift compatibility FFIgen interacts with Swift through Objective-C. Pure Swift features (such as structs, generics, or enums associated with values) -are not supported, and classes must be annotated with `@objc`. -For pure Swift interop, explore the experimental [`package:swiftgen`][swiftgen-pub] -package. +aren't supported, and classes must be annotated with `@objc`. +For pure Swift interop, explore the Dart team's experimental +[`package:swiftgen`][swiftgen-pub] package, +or use [Pigeon](/interop/pigeon) for production Swift plugins. ## Resources @@ -139,3 +142,4 @@ refer to the following resources: [objc-pub]: {{site.pub-pkg}}/objective_c [swiftgen-pub]: {{site.pub-pkg}}/swiftgen [ffigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/ffigen/example +[objc-gc-doc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_gc.md diff --git a/sites/docs/src/content/interop/index.md b/sites/docs/src/content/interop/index.md index 068c4dc5167..ab56c0b63e1 100644 --- a/sites/docs/src/content/interop/index.md +++ b/sites/docs/src/content/interop/index.md @@ -55,7 +55,7 @@ Follow the steps below to walk through the decision tree in detail: ### Step 2: Evaluate Pigeon for Flutter plugins -If you are building a Flutter plugin, +If you're building a Flutter plugin, first verify whether [Pigeon](/interop/pigeon) satisfies your technical constraints. @@ -99,8 +99,9 @@ Depending on how you plan to share your code, choose the option that best suits your use case. :::note -To learn about the difference between a package and a plugin, -refer to [Using packages](/packages-and-plugins/using-packages). +To learn about the difference between a [package](/resources/glossary#package) +and a [plugin](/resources/glossary#plugin), +refer to the [Flutter glossary](/resources/glossary). ::: - **In-app integration.** @@ -140,16 +141,16 @@ The following table compares two interop solutions: | Target language | Direct interop | Pigeon | | --- | --- | --- | -| **C** | [FFIgen](/interop/ffigen) | Not recommended. Supported with platform channels, but not maintained by the Flutter team. | -| **C++ with `extern "C"`** | [FFIgen](/interop/ffigen) | (Refer to C) | -| **C++ without `extern "C"`** | FFIgen (not yet supported) | Supported with platform channels, but not maintained by the Flutter team. | -| **Rust with `extern "C"`** | [FFIgen](/interop/ffigen) | (Refer to C) | +| **C** | [FFIgen](/interop/ffigen) | Supported on Linux (GObject) with platform channels | +| **C++ with `extern "C"`** | [FFIgen](/interop/ffigen) | Supported on Windows with platform channels | +| **C++ without `extern "C"`** | FFIgen (not yet supported) | Supported on Windows with platform channels | +| **Rust with `extern "C"`** | [FFIgen](/interop/ffigen) | Not supported directly | | **Rust without `extern "C"`** | [Flutter Rust Bridge][frb] | Not supported | -| **Go with `extern "C"`** | [FFIgen](/interop/ffigen) | (Refer to C) | +| **Go with `extern "C"`** | [FFIgen](/interop/ffigen) | Not supported directly | | **Go without `extern "C"`** | Not supported | Not supported | -| **Java** | [JNIgen](/interop/jnigen) | Supported through Kotlin | +| **Java** | [JNIgen](/interop/jnigen) | Supported through Kotlin (native interop) or directly with platform channels | | **Kotlin** | [JNIgen](/interop/jnigen) | Supported (recommended) | -| **Objective-C** | [FFIgen](/interop/ffigen) | Supported through Swift | +| **Objective-C** | [FFIgen](/interop/ffigen) | Supported through Swift (native interop) or directly with platform channels | | **Swift without Obj-C compatibility annotations** | [`package:swiftgen`][swiftgen-pub] (experimental) | Supported (recommended) | {:.table .table-striped} diff --git a/sites/docs/src/content/interop/jnigen.md b/sites/docs/src/content/interop/jnigen.md index 838e58fbe22..ef6e336e97a 100644 --- a/sites/docs/src/content/interop/jnigen.md +++ b/sites/docs/src/content/interop/jnigen.md @@ -9,7 +9,7 @@ description: >- JNIgen (Java Native Interface generator) is a code generator tool that creates type-safe Dart bindings for Java and Kotlin libraries and APIs. -This allows you to call Android APIs directly and synchronously from your +This allows calling Android APIs directly and synchronously from your Dart code. ## When to use JNIgen @@ -28,7 +28,7 @@ instead. ## Developer flow -Generating bindings with JNIgen follows this general flow: +Generate bindings with JNIgen using the following general flow: 1. Reference the native classes or JARs. 1. Write the JNIgen configuration script. @@ -57,7 +57,7 @@ to learn how to manage native memory lifetimes and execute native calls without blocking the UI thread. :::note -If the native API changes, you need to update your configuration script and +If the native API changes, update your configuration script and regenerate the bindings. ::: @@ -76,11 +76,11 @@ JNIgen: ### Threading considerations -JNIgen calls execute synchronously on the calling thread. +JNIgen calls run synchronously on the calling thread. Watch out for two common scenarios: -- Calling platform APIs that must run on the platform thread. - Use `runOnPlatformThread` from `dart:ui`. +- When calling platform APIs that must run on the platform thread, + use [`runOnPlatformThread`][run-on-platform-thread] from `dart:ui`. - Dart callbacks from Java run on the isolate that created them. This can lead to deadlocks if a blocked Java thread waits for a blocked Dart thread. @@ -96,7 +96,7 @@ when implementing interfaces. ### JVM runtime type checks JNI wrappers are Dart extension types that compile down to `JObject` -at runtime, so Dart's native `is` and `as` keywords do not check the Java +at runtime, so Dart's native `is` and `as` keywords don't check the Java runtime type. Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. @@ -121,3 +121,4 @@ refer to the following resources: [jnigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example [permissions-demo]: {{site.repo.demos}}/tree/main/native_interop_demos/permissions_plugin [toast-demo]: {{site.repo.demos}}/tree/main/native_interop_demos/android_toast_demo +[run-on-platform-thread]: {{site.api}}/flutter/dart-ui/runOnPlatformThread.html diff --git a/sites/docs/src/content/interop/pigeon.md b/sites/docs/src/content/interop/pigeon.md index a9f429c886e..cd43b1eac18 100644 --- a/sites/docs/src/content/interop/pigeon.md +++ b/sites/docs/src/content/interop/pigeon.md @@ -33,9 +33,9 @@ direct native bindings (using FFI on iOS and macOS, and JNI on Android). Use this option if your use case requires any of the following: - You need synchronous execution on your native platform. -- You are doing high-frequency data streaming +- You're doing high-frequency data streaming (for example, audio or camera frames). -- You are calling native code from background Dart isolates. +- You're calling native code from background Dart isolates. ### Pigeon with platform channels @@ -49,7 +49,7 @@ Use this option if your use case requires any of the following: ## Developer flow -The developer experience with Pigeon follows a structured cycle: +Generate type-safe bindings with Pigeon using the following steps: 1. Create the Pigeon definition file. 1. Generate the code. @@ -64,19 +64,20 @@ Next, run the Pigeon generator tool, which parses your Pigeon definition file and produces the necessary code for both Dart and your target native platform. :::note -If you're using the Pigeon with native interop communication option, -set `appDirectory`, `swiftOptions: SwiftOptions(useFfi: true)`, and -`kotlinOptions: KotlinOptions(useJni: true)` in the `@ConfigurePigeon` +If you're using Pigeon with the native interop communication option, +set `appDirectory`, `swiftOptions: SwiftOptions(useFfi: true, ffiModuleName: 'Runner')`, +and `kotlinOptions: KotlinOptions(useJni: true)` in the `@ConfigurePigeon` section of your Pigeon definition file. For complete prerequisites and build configuration, refer to the [native interop guide][pigeon-native-guide]. ::: -After the code is generated, you implement the generated native interfaces +After the code is generated, implement the generated native interfaces in the platform-specific directories of your Flutter project (such as `android/`, `ios/`, or `macos/`) and register them with the Flutter engine. -Finally, you call the generated Dart classes from your Flutter app, +Finally, call the generated Dart classes from your Flutter app +(using `.createWithNativeInteropApi()` when using native interop), or invoke the generated native APIs to call back into your Dart code. If you need to modify the API, first modify the Pigeon definition file, @@ -105,13 +106,16 @@ use [FFIgen](/interop/ffigen) or ### Inheritance -Pigeon does not support complex inheritance. +Pigeon doesn't support complex inheritance. Flatten your data models where possible. ### (Pigeon with platform channels only) Async errors -For `@async` methods, native code must return errors through the provided -callback. +In Kotlin and Swift, `@async` generates `suspend` and `async throws` +functions where native code throws `FlutterError` or `PigeonError` directly. +For `@asyncCallback` methods—or when using the Java, Objective-C, C++, +or GObject generators—native code must return errors through the provided +completion callback. ## Resources diff --git a/sites/docs/src/content/platform-integration/platform-channels.md b/sites/docs/src/content/platform-integration/platform-channels.md index 419361e70fe..9c2bc7d253e 100644 --- a/sites/docs/src/content/platform-integration/platform-channels.md +++ b/sites/docs/src/content/platform-integration/platform-channels.md @@ -1425,7 +1425,6 @@ in the Flutter ecosystem, see [publishing packages][]. [`FirestoreMessageCodec`]: {{site.repo.flutterfire}}/blob/master/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/firestore_message_codec.dart [developing packages]: /packages-and-plugins/developing-packages [Engine Embedder APIs]: {{site.dart.api}}/index.html#more-documentation -[`Pigeon`]: {{site.pub-pkg}}/pigeon [plugins]: /packages-and-plugins/developing-packages#plugin [dispatch queue]: {{site.apple-dev}}/documentation/dispatch/dispatchqueue [`/examples/platform_channel/`]: {{site.repo.flutter}}/tree/main/examples/platform_channel From bf42c2f814f53a20e0f174b2b0cff922ce2fb12a Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Thu, 29 Oct 2026 01:52:58 +0000 Subject: [PATCH 10/16] Use lowercase `add` for FFIgen example directory --- sites/docs/src/content/interop/ffigen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/docs/src/content/interop/ffigen.md b/sites/docs/src/content/interop/ffigen.md index 4c3c6999b4a..b7ff511b586 100644 --- a/sites/docs/src/content/interop/ffigen.md +++ b/sites/docs/src/content/interop/ffigen.md @@ -69,7 +69,7 @@ To explore a complete implementation of this flow, including how to configure the generator, refer to the [official FFIgen examples][ffigen-examples]. -The `Add` example highlights the general flow with a toy native API +The `add` example highlights the general flow with a toy native API (one function in a single header file). The `code_assets` examples highlight real-world examples like From 5c9d908915fc87d170a68a801760ddafa40b32b1 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Thu, 29 Oct 2026 05:15:16 +0000 Subject: [PATCH 11/16] Rename decision tree steps to questions and simplify Swift experimental note --- sites/docs/src/content/interop/ffigen.md | 2 +- sites/docs/src/content/interop/index.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sites/docs/src/content/interop/ffigen.md b/sites/docs/src/content/interop/ffigen.md index b7ff511b586..01967a35aaa 100644 --- a/sites/docs/src/content/interop/ffigen.md +++ b/sites/docs/src/content/interop/ffigen.md @@ -14,7 +14,7 @@ This allows calling Apple platform APIs (iOS and macOS) directly and synchronously from your Dart code. :::note -Direct Swift interop is in experimental support and is likely to change. +Direct Swift interop is in experimental support. ::: ## When to use FFIgen diff --git a/sites/docs/src/content/interop/index.md b/sites/docs/src/content/interop/index.md index ab56c0b63e1..18311c07644 100644 --- a/sites/docs/src/content/interop/index.md +++ b/sites/docs/src/content/interop/index.md @@ -17,14 +17,14 @@ or read on to learn about the native interop layers. ```mermaid flowchart TD - Start(["Step 1: What is your primary goal?"]) --> GoalA["Goal A: Platform integration
    (Swift, Kotlin, Java, Obj-C)"] + Start(["Question 1: What is your primary goal?"]) --> GoalA["Goal A: Platform integration
    (Swift, Kotlin, Java, Obj-C)"] Start --> GoalB["Goal B: C-ABI wrapping
    (C, C++, Rust, Go)"] Start --> GoalC["Goal C: Pure Dart package
    (No Flutter framework)"] GoalB --> FFIgen["Use FFIgen
    (or Flutter Rust Bridge)"]:::directNode GoalC --> Direct["Use FFIgen or JNIgen"]:::directNode - GoalA --> EvalPigeon{"Step 2: Do high-complexity
    constraints apply?"} + GoalA --> EvalPigeon{"Question 2: Do high-complexity
    constraints apply?"} EvalPigeon -->|"No (most common)"| Pigeon["Use Pigeon
    (Recommended default)"]:::recommendedNode EvalPigeon -->|"Yes (advanced)"| DirectInterop["Use FFIgen or JNIgen"]:::directNode @@ -34,14 +34,14 @@ flowchart TD --- -Follow the steps below to walk through the decision tree in detail: +Answer the following questions to walk through the decision tree in detail: -### Step 1: Determine your primary goal +### Question 1: What is your primary goal? **Goal A: Platform integration** : Communicate with native OS features (Swift, Kotlin, Java, or Objective-C) or expose an API surface. - Proceed to [Step 2: Evaluate Pigeon for Flutter plugins](#step-2-evaluate-pigeon-for-flutter-plugins). + Proceed to [Question 2: Does Pigeon fit your plugin constraints?](#question-2-does-pigeon-fit-your-plugin-constraints). **Goal B: C-ABI wrapping** : Wrap an existing native library (C, C++, Rust, or Go). @@ -53,7 +53,7 @@ Follow the steps below to walk through the decision tree in detail: Use [FFIgen](/interop/ffigen) or [JNIgen](/interop/jnigen). -### Step 2: Evaluate Pigeon for Flutter plugins +### Question 2: Does Pigeon fit your plugin constraints? If you're building a Flutter plugin, first verify whether [Pigeon](/interop/pigeon) satisfies From e31b983cb455520074e66d474bdb405001a44d4a Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Thu, 29 Oct 2026 05:22:53 +0000 Subject: [PATCH 12/16] Align Question 2 heading with diagram and reduce note callouts on index.md --- sites/docs/src/content/interop/index.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/sites/docs/src/content/interop/index.md b/sites/docs/src/content/interop/index.md index 18311c07644..ff3d3990b51 100644 --- a/sites/docs/src/content/interop/index.md +++ b/sites/docs/src/content/interop/index.md @@ -41,7 +41,7 @@ Answer the following questions to walk through the decision tree in detail: **Goal A: Platform integration** : Communicate with native OS features (Swift, Kotlin, Java, or Objective-C) or expose an API surface. - Proceed to [Question 2: Does Pigeon fit your plugin constraints?](#question-2-does-pigeon-fit-your-plugin-constraints). + Proceed to [Question 2: Do high-complexity constraints apply?](#question-2-do-high-complexity-constraints-apply). **Goal B: C-ABI wrapping** : Wrap an existing native library (C, C++, Rust, or Go). @@ -53,7 +53,7 @@ Answer the following questions to walk through the decision tree in detail: Use [FFIgen](/interop/ffigen) or [JNIgen](/interop/jnigen). -### Question 2: Does Pigeon fit your plugin constraints? +### Question 2: Do high-complexity constraints apply? If you're building a Flutter plugin, first verify whether [Pigeon](/interop/pigeon) satisfies @@ -97,12 +97,9 @@ and provides safer guardrails. Depending on how you plan to share your code, choose the option that best suits your use case. - -:::note To learn about the difference between a [package](/resources/glossary#package) and a [plugin](/resources/glossary#plugin), refer to the [Flutter glossary](/resources/glossary). -::: - **In-app integration.** Write your native interop code directly inside your Flutter app without @@ -114,19 +111,14 @@ refer to the [Flutter glossary](/resources/glossary). For more details, refer to [Developing plugin packages](/packages-and-plugins/developing-packages#plugin). - **FFI package.** - A package that compiles and bundles native C or C++ code using Dart build - hooks and [`dart:ffi`](/interop/dart-ffi). + A package that compiles and bundles native C or C++ code (or pre-compiled + binaries) using [Dart build hooks](/interop/bind-native-code) and + [`dart:ffi`](/interop/dart-ffi). This template also includes an FFIgen configuration to regenerate Dart bindings (`flutter create --template=package_ffi`). For more details, refer to [Developing FFI packages](/packages-and-plugins/developing-packages#ffi). -:::note -If you're using pre-compiled binaries (such as `.so` files on Android or -`.dylib` files on iOS and macOS), use `CodeAsset` in your `hook/build.dart` -script to bundle them into your package. -::: - For more details on package authoring, refer to [Developing packages and plugins](/packages-and-plugins/developing-packages). From 4cace31dbaa55e3690b8604a31ae1db5632183a5 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Fri, 30 Oct 2026 04:29:52 +0000 Subject: [PATCH 13/16] Update call-jetpack-apis.md to link to new native interop docs --- .../android/call-jetpack-apis.md | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md index d2476b9145d..82f5aab5706 100644 --- a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md +++ b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md @@ -54,21 +54,16 @@ two options. The most direct and efficient way to invoke native APIs is by calling the API directly, via FFI. This links your Dart executable to any specified native code at compile-time, allowing you to -call it directly from the UI thread through a small amount of glue -code. In most cases, [ffigen][ffigen] or [jnigen][jnigen] are -helpful in writing this glue code. +call it directly from the UI thread through generated bindings. +On Android, [JNIgen][jnigen] generates Dart bindings for Java and +Kotlin APIs, and [FFIgen][ffigen] generates bindings for C APIs. -For complete guidance on directly calling native code from -your Flutter app, see the [FFI documentation][ffi]. +For complete guidance on calling native code directly from +your Flutter app, consult the [Native interop documentation][interop]. -In the coming months, the Dart team hopes to make this process -easier with direct support for calling native APIs using the -FFI approach, but without any need for the developer to write -glue code. - -[ffi]: {{site.dart-site}}/interop/c-interop -[ffigen]: {{site.pub}}/packages/ffigen -[jnigen]: {{site.pub}}/packages/jnigen +[interop]: /interop +[ffigen]: /interop/ffigen +[jnigen]: /interop/jnigen ### Add a MethodChannel @@ -81,11 +76,11 @@ might or might not matter to you, depending on your use case. As with FFI and direct calls to native code, using a `MethodChannel` requires a small amount of glue code to translate your Dart objects into native objects, and then back again. In most cases, -[`pkg:pigeon`][pigeon] is helpful in writing this glue code. +[Pigeon][pigeon] is helpful in generating this glue code. For complete guidance on adding MethodChannels to your Flutter -app, see the [`MethodChannel`s documentation][methodchannels]. +app, consult the [`MethodChannel`s documentation][methodchannels]. -[methodchannels]: /platform-integration/platform-channels +[methodchannels]: /interop/platform-channels [methodchannels-api-docs]: {{site.api}}/flutter/services/MethodChannel-class.html -[pigeon]: {{site.pub}}/packages/pigeon +[pigeon]: /interop/pigeon From 650cbb7c91172595afded3e5996360f442a9fbd6 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Fri, 30 Oct 2026 04:39:52 +0000 Subject: [PATCH 14/16] Fix platform-channels link in call-jetpack-apis.md --- .../content/platform-integration/android/call-jetpack-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md index 82f5aab5706..d1d78c14dce 100644 --- a/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md +++ b/sites/docs/src/content/platform-integration/android/call-jetpack-apis.md @@ -81,6 +81,6 @@ into native objects, and then back again. In most cases, For complete guidance on adding MethodChannels to your Flutter app, consult the [`MethodChannel`s documentation][methodchannels]. -[methodchannels]: /interop/platform-channels +[methodchannels]: /platform-integration/platform-channels [methodchannels-api-docs]: {{site.api}}/flutter/services/MethodChannel-class.html [pigeon]: /interop/pigeon From fcf3a7eef68c8cf7a48873b69a2a7fe30db02f13 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Fri, 30 Oct 2026 15:21:11 +0000 Subject: [PATCH 15/16] Address Liam's review comments and add cupertino_http/cronet_http examples --- sites/docs/src/content/interop/dart-ffi.md | 31 +++++++++++----------- sites/docs/src/content/interop/ffigen.md | 12 ++++++++- sites/docs/src/content/interop/jnigen.md | 21 ++++++++++++--- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/sites/docs/src/content/interop/dart-ffi.md b/sites/docs/src/content/interop/dart-ffi.md index 7518960e775..d3fdbda3655 100644 --- a/sites/docs/src/content/interop/dart-ffi.md +++ b/sites/docs/src/content/interop/dart-ffi.md @@ -14,38 +14,37 @@ or [JNIgen](/interop/jnigen), `dart:ffi` is the underlying technology that powers these tools and enables native memory interop. -## When to use `dart:ffi` +## When to use `dart:ffi` and `package:ffi` -When you use FFIgen or JNIgen to generate Dart bindings, -the generated code doesn't automatically convert Dart objects -like `String` or `List` into native formats. Dart memory is automatically garbage collected while native C memory is -manually managed, so use `dart:ffi` to bridge this gap. +manually managed, so use `dart:ffi` and `package:ffi` to bridge this gap. -The two most common scenarios when `dart:ffi` is needed are -memory allocation and data conversion. +The two most common scenarios when `package:ffi` is needed alongside `dart:ffi` +are memory allocation and C string conversion. -With memory allocation, use `dart:ffi` to allocate space on the system heap +With memory allocation, use `package:ffi` to allocate space on the system heap for native arguments, and manually free that memory when done to prevent memory leaks. -With data conversion, use `dart:ffi` to convert Dart data types -so they can be read by native code. +With data conversion, use `package:ffi` to convert Dart strings into C strings +(`Pointer` or `Pointer`) so they can be read by native C code. ## Core tools: `dart:ffi` and `package:ffi` -To write interop code, use two libraries: +To write C interop code, use two libraries: -- **`dart:ffi`:** Built into the core Dart SDK. - It provides native representation types and lookup signatures. -- **`package:ffi`:** A required dependency for working with `dart:ffi`. - It provides memory allocation methods and conversion helpers. +- **`dart:ffi`:** Built into the core Dart SDK and required for doing FFI + interop. It provides native representation types, pointers, structs, and + function lookup signatures. +- **`package:ffi`:** A companion package that contains utilities helpful in + common C interop use cases, such as memory allocation (`calloc` and `malloc`) + and C string conversion helpers. ## Concurrency in Flutter FFI calls are synchronous and run on the thread on which they're called. If you invoke a heavy or blocking C function on your Flutter app's -main UI thread, your app freezes or drops frames. +main UI thread, your app may freeze or drop frames. To prevent this, execute blocking FFI calls inside a background Dart isolate using `Isolate.run`. diff --git a/sites/docs/src/content/interop/ffigen.md b/sites/docs/src/content/interop/ffigen.md index 01967a35aaa..7a6a685aa64 100644 --- a/sites/docs/src/content/interop/ffigen.md +++ b/sites/docs/src/content/interop/ffigen.md @@ -75,6 +75,10 @@ The `add` example highlights the general flow with a toy native API The `code_assets` examples highlight real-world examples like system hostname lookups and audio playback. +For a production package on pub.dev that uses FFIgen to wrap Apple's +`NSURLSession` API, check out [`package:cupertino_http`][cupertino-http-pub] +([source on GitHub][cupertino-http-src]). + ## Constraints and limitations Some general considerations when working with FFIgen: @@ -112,13 +116,15 @@ as shown in the [Objective-C memory management guide][objc-gc-doc]. FFIgen wrappers enforce Dart static types, but the underlying Objective-C object might have a different runtime subclass, so Dart's `is` and `as` keywords won't work as expected. -Use `.isA()` and `.as()` instead. +Use `.isA()` and `.as()` instead, as shown in the +[Objective-C runtime types guide][objc-runtime-types-doc]. ### Objective-C multithreading UI-related Objective-C calls must run on the iOS main platform thread. Also, some types of callbacks can crash your app if they're executed on the wrong thread. +To learn more, consult the [Objective-C threading guide][objc-threading-doc]. ### Swift compatibility @@ -141,5 +147,9 @@ refer to the following resources: [ffigen-pub]: {{site.pub-pkg}}/ffigen [objc-pub]: {{site.pub-pkg}}/objective_c [swiftgen-pub]: {{site.pub-pkg}}/swiftgen +[cupertino-http-pub]: {{site.pub-pkg}}/cupertino_http +[cupertino-http-src]: https://github.com/dart-lang/http/tree/master/pkgs/cupertino_http [ffigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/ffigen/example [objc-gc-doc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_gc.md +[objc-runtime-types-doc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_runtime_types.md +[objc-threading-doc]: https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/objc_threading.md diff --git a/sites/docs/src/content/interop/jnigen.md b/sites/docs/src/content/interop/jnigen.md index ef6e336e97a..01d10496bd9 100644 --- a/sites/docs/src/content/interop/jnigen.md +++ b/sites/docs/src/content/interop/jnigen.md @@ -69,6 +69,10 @@ refer to the [`permissions_plugin` demo][permissions-demo] and the [`android_toast_demo`][toast-demo] in the official Flutter native interop demos repository. +For a production package on pub.dev that uses JNIgen to wrap the Android +Cronet HTTP library, check out [`package:cronet_http`][cronet-http-pub] +([source on GitHub][cronet-http-src]). + ## Constraints and limitations Keep these general considerations and limitations in mind when working with @@ -85,27 +89,32 @@ Watch out for two common scenarios: This can lead to deadlocks if a blocked Java thread waits for a blocked Dart thread. +To learn more, consult the [JNIgen threading guide][jnigen-threading-doc]. + ### Lifecycle of Java objects JNI wrappers use Java global references. There is a hard limit of 50,000 active references. Call `.release()` or use `.using()` blocks to prevent memory leaks. Use `WeakReference` to break reference cycles between Dart and Java GCs -when implementing interfaces. +when implementing interfaces, as shown in the +[JNI object lifecycle guide][jnigen-lifecycle-doc]. ### JVM runtime type checks JNI wrappers are Dart extension types that compile down to `JObject` at runtime, so Dart's native `is` and `as` keywords don't check the Java runtime type. -Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead. +Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead, as shown in the +[Java runtime types guide][jnigen-runtime-types-doc]. ### General debugging Tracking down JNI memory allocation bugs like `DoubleReleaseError` and `UseAfterReleaseError` can be difficult. Set `Jni.captureStackTraceOnRelease = true` to log stack traces -and help trace leaks. +and help trace leaks, as explained in the +[JNIgen debugging guide][jnigen-debugging-doc]. ## Resources @@ -118,7 +127,13 @@ refer to the following resources: [jnigen-pub]: {{site.pub-pkg}}/jnigen [jni-pub]: {{site.pub-pkg}}/jni +[cronet-http-pub]: {{site.pub-pkg}}/cronet_http +[cronet-http-src]: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http [jnigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example [permissions-demo]: {{site.repo.demos}}/tree/main/native_interop_demos/permissions_plugin [toast-demo]: {{site.repo.demos}}/tree/main/native_interop_demos/android_toast_demo [run-on-platform-thread]: {{site.api}}/flutter/dart-ui/runOnPlatformThread.html +[jnigen-threading-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/threading.md +[jnigen-lifecycle-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/lifecycle.md +[jnigen-runtime-types-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/java_runtime_types.md +[jnigen-debugging-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/debugging.md From df2e4db0a4e37301b4491ff67243396bb15cc564 Mon Sep 17 00:00:00 2001 From: Kevin Lamenzo Date: Thu, 29 Oct 2026 06:24:40 +0000 Subject: [PATCH 16/16] Add interactive component for native interop docs Replaces the duplicated Mermaid flowchart and text walkthrough on the Native Interop overview page (/interop) with a single YAML-driven custom Jaspr component. - Adds DecisionTree and DecisionTreeComponent in package:site_shared/components/common/decision_tree.dart using server-rendered HTML + progressive client enhancement (avoiding @client build_runner codegen churn). - Adds responsive, theme-aware styling in package:site_shared/_sass/components/_decision-tree.scss using existing site CSS custom properties, including 'Show all paths' and @media print support. - Wires client interactivity (option selection, downstream step/recommendation reveal, reset, and 'Show all paths' toggle) in sites/docs/lib/src/client/global_scripts.dart. - Updates sites/docs/src/content/interop/index.md to author the decision tree once in inline YAML. --- packages/site_shared/README.md | 3 +- .../lib/_sass/components/_decision-tree.scss | 292 +++++++++ .../lib/components/common/decision_tree.dart | 573 ++++++++++++++++++ sites/docs/lib/_sass/_site.scss | 1 + sites/docs/lib/main.server.dart | 2 + sites/docs/lib/src/client/global_scripts.dart | 182 ++++++ sites/docs/src/content/interop/index.md | 194 +++--- 7 files changed, 1169 insertions(+), 78 deletions(-) create mode 100644 packages/site_shared/lib/_sass/components/_decision-tree.scss create mode 100644 packages/site_shared/lib/components/common/decision_tree.dart diff --git a/packages/site_shared/README.md b/packages/site_shared/README.md index db75ce7d779..040523d2780 100644 --- a/packages/site_shared/README.md +++ b/packages/site_shared/README.md @@ -18,7 +18,8 @@ build documentation websites using Dart, Jaspr, and Jaspr Content: - **UI components** (`lib/components`): Reusable, modular components built for use across documentation pages. - **Common components** (`lib/components/common`): - Everyday UI elements such as breadcrumbs, buttons, code blocks, and more. + Everyday UI elements such as breadcrumbs, buttons, code blocks, + interactive decision trees (`decision_tree.dart`), and more. - **Layout components** (`lib/components/layout`): Structural layout elements like theme switchers, site switchers, banners, and menu toggles. diff --git a/packages/site_shared/lib/_sass/components/_decision-tree.scss b/packages/site_shared/lib/_sass/components/_decision-tree.scss new file mode 100644 index 00000000000..b0bbdad199f --- /dev/null +++ b/packages/site_shared/lib/_sass/components/_decision-tree.scss @@ -0,0 +1,292 @@ +// Copyright 2025 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +.decision-tree { + background-color: var(--site-raised-bgColor); + border: 1px solid var(--site-inset-borderColor); + border-radius: var(--site-radius); + margin: 1.5rem 0; + overflow: hidden; + + .decision-tree-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.75rem; + padding: 0.75rem 1.25rem; + background-color: var(--site-inset-bgColor); + border-bottom: 1px solid var(--site-inset-borderColor); + font-family: var(--site-ui-fontFamily); + + .decision-tree-toolbar-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + font-size: 0.9375rem; + color: var(--site-base-fgColor); + + .material-symbols { + color: var(--site-primary-color); + font-size: 1.25rem; + } + } + + .decision-tree-toolbar-actions { + display: flex; + align-items: center; + gap: 0.5rem; + } + } + + .decision-tree-steps { + display: flex; + flex-direction: column; + } + + .decision-tree-step { + padding: 1.25rem 1.5rem; + border-bottom: 1px solid var(--site-inset-borderColor); + + &[hidden] { + display: none; + } + + .decision-tree-step-header { + display: flex; + flex-direction: column; + gap: 0.375rem; + margin-bottom: 0.75rem; + } + + .decision-tree-step-badge { + display: inline-flex; + align-items: center; + align-self: flex-start; + padding: 0.125rem 0.625rem; + border-radius: 999px; + background-color: var(--site-primary-color-bg); + color: var(--site-primary-color); + font-family: var(--site-ui-fontFamily); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: uppercase; + } + + .decision-tree-question { + margin: 0; + font-family: var(--site-font-family-alt); + font-size: 1.25rem; + font-weight: 500; + line-height: 1.4; + color: var(--site-base-fgColor); + } + + .decision-tree-step-description { + margin-bottom: 0.75rem; + + p:last-child { + margin-bottom: 0; + } + } + + .decision-tree-checklist { + margin: 0.5rem 0 1rem; + padding-left: 1.5rem; + + li { + margin-bottom: 0.375rem; + } + } + + .decision-tree-callout { + margin: 0.75rem 0 1rem; + } + } + + .decision-tree-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 0.875rem; + margin-top: 1rem; + } + + .decision-tree-option { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + gap: 0.5rem; + padding: 1rem 1.125rem; + text-align: left; + background-color: var(--site-inset-bgColor); + border: 1.5px solid var(--site-inset-borderColor); + border-radius: var(--site-radius); + color: var(--site-base-fgColor); + cursor: pointer; + transition: + border-color 0.15s ease, + background-color 0.15s ease, + box-shadow 0.15s ease; + + &:hover { + background-color: var(--site-hover-bgColor); + border-color: var(--site-primary-color); + } + + &:focus-visible { + outline: 2px solid var(--site-primary-color); + outline-offset: 2px; + } + + &.is-selected, + &[aria-pressed='true'] { + background-color: var(--site-primary-color-bg); + border-color: var(--site-primary-color); + box-shadow: 0 0 0 1px var(--site-primary-color); + + .decision-tree-option-icon, + .decision-tree-option-arrow { + color: var(--site-primary-color); + } + } + + .decision-tree-option-header { + display: flex; + align-items: center; + width: 100%; + gap: 0.625rem; + } + + .decision-tree-option-icon { + color: var(--site-primary-color); + font-size: 1.25rem; + flex-shrink: 0; + } + + .decision-tree-option-label { + flex: 1; + font-family: var(--site-ui-fontFamily); + font-size: 1rem; + font-weight: 600; + line-height: 1.35; + } + + .decision-tree-option-arrow { + font-size: 1.125rem; + opacity: 0.7; + flex-shrink: 0; + transition: transform 0.15s ease; + } + + &:hover .decision-tree-option-arrow { + transform: translateX(2px); + opacity: 1; + } + + .decision-tree-option-subtitle { + font-size: 0.875rem; + line-height: 1.45; + opacity: 0.85; + } + } + + .decision-tree-recommendations { + display: flex; + flex-direction: column; + } + + .decision-tree-recommendation { + padding: 1.5rem; + background-color: var(--site-alert-tip-bg); + border-top: 2px solid var(--site-alert-tip-borderColor); + + &[hidden] { + display: none; + } + + .decision-tree-recommendation-header { + display: flex; + align-items: flex-start; + gap: 0.75rem; + margin-bottom: 0.75rem; + } + + .decision-tree-recommendation-icon { + color: var(--site-alert-tip-color); + font-size: 1.75rem; + margin-top: 0.125rem; + flex-shrink: 0; + } + + .decision-tree-recommendation-titles { + display: flex; + flex-direction: column; + gap: 0.125rem; + } + + .decision-tree-recommendation-eyebrow { + font-family: var(--site-ui-fontFamily); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.03em; + text-transform: uppercase; + color: var(--site-alert-tip-color); + } + + .decision-tree-recommendation-title { + margin: 0; + font-family: var(--site-font-family-alt); + font-size: 1.375rem; + font-weight: 600; + line-height: 1.3; + color: var(--site-base-fgColor); + } + + .decision-tree-recommendation-body { + margin-bottom: 1rem; + + p:last-child { + margin-bottom: 0; + } + } + + .decision-tree-callout { + margin: 1rem 0; + } + + .decision-tree-recommendation-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1rem; + } + } + + // When "Show all paths" is active or when printing, reveal all steps and + // recommendations so readers can compare every path at a glance. + &.show-all { + .decision-tree-step[hidden], + .decision-tree-recommendation[hidden] { + display: block; + } + + .decision-tree-recommendation + .decision-tree-recommendation { + border-top: 1px solid var(--site-inset-borderColor); + } + } + + @media print { + .decision-tree-toolbar-actions { + display: none; + } + + .decision-tree-step[hidden], + .decision-tree-recommendation[hidden] { + display: block !important; + } + } +} diff --git a/packages/site_shared/lib/components/common/decision_tree.dart b/packages/site_shared/lib/components/common/decision_tree.dart new file mode 100644 index 00000000000..b0b7c23da4c --- /dev/null +++ b/packages/site_shared/lib/components/common/decision_tree.dart @@ -0,0 +1,573 @@ +// Copyright 2025 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Interactive decision tree component for guiding readers through a sequence +/// of architectural or tooling questions toward a tailored recommendation. +/// +/// ## How it works +/// +/// `` follows the same server-rendered + progressive-enhancement +/// pattern as ``, ``, and ``: +/// +/// 1. **Server rendering (`DecisionTree` / `DecisionTreeComponent`)**: +/// At build time, `DecisionTree` parses YAML content (either inline inside +/// `...` or referenced by `id` from page data +/// such as `src/data/decision_tree.yaml`) and renders semantic HTML for all +/// steps and recommendation cards. This avoids `@client` island codegen +/// (`build_runner` / `main.*.options.dart`), keeps package/SDK upgrades +/// friction-free, and allows nested Markdown (`DashMarkdown`) for links, +/// inline code, lists, and callouts. +/// 2. **Client interactivity (`_setUpDecisionTrees` in `global_scripts.dart`)**: +/// In the browser, selecting an option highlights the active choice and +/// reveals either the next question step (`next_step`) or the matching +/// recommendation card (`recommendation`). Users can change an earlier +/// answer at any time (which automatically clears downstream selections), +/// click **Start over** to reset, or click **Show all paths** to compare +/// every step and recommendation side by side. +/// +/// ## Authoring a decision tree in Markdown +/// +/// Embed `` directly in a Markdown page with inline YAML: +/// +/// ```markdown +/// +/// steps: +/// - id: q1 +/// question: Do you need to write a small amount of native glue code? +/// description: >- +/// Select the option that best matches your project constraints. +/// callout: +/// type: important +/// title: Migrating from Method Channels? +/// content: >- +/// If you are migrating an existing plugin, select **Yes**. +/// options: +/// - label: "Yes: I want to write small glue code" +/// subtitle: Best for calling platform-specific APIs. +/// icon: code +/// recommendation: pigeon +/// - label: "No: I want direct bindings" +/// subtitle: Best for large existing libraries. +/// icon: terminal +/// next_step: q2 +/// # Recommendations +/// recommendations: +/// - id: pigeon +/// title: Use Pigeon +/// icon: check_circle +/// description: >- +/// Use **[Pigeon](/interop/pigeon)** to generate type-safe channels. +/// link: /interop/pigeon +/// link_text: Learn about Pigeon +/// +/// ``` +/// +/// > **Important (CommonMark HTML block rule):** Do not leave completely blank +/// > lines inside an inline `...` block, because +/// > CommonMark ends raw HTML blocks at the first blank line. Use `#` YAML +/// > comment lines for visual spacing between sections, or store the YAML in +/// > `src/data/decision_tree.yaml` and reference it with +/// > ``. +library; + +import 'package:jaspr/dom.dart'; +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_content/jaspr_content.dart'; +import 'package:yaml/yaml.dart'; + +import '../../src/markdown/markdown_parser.dart'; +import '../../util.dart'; +import 'button.dart'; +import 'material_icon.dart'; + +/// Custom Markdown component that parses `` blocks. +final class DecisionTree extends CustomComponent { + const DecisionTree() : super.base(); + + @override + Component? create(Node node, NodesBuilder builder) { + if (node is! ElementNode || node.tag.toLowerCase() != 'decisiontree') { + return null; + } + + final title = node.attributes['title']; + + // Support loading a decision tree by ID from page/site data + // (for example, `src/data/decision_tree.yaml` or page frontmatter). + if (node.attributes['id'] case final String treeId when treeId.isNotEmpty) { + return Builder( + builder: (context) { + final trees = + context.page.data['decision_tree'] as Map?; + if (trees?[treeId] case final Map treeData) { + return DecisionTreeComponent( + title: title, + model: DecisionTreeModel.fromMap(treeData), + ); + } + + throw ArgumentError( + 'Failed to find decision_tree entry with id "$treeId" in page data.', + ); + }, + ); + } + + if (node.children?.whereType().isNotEmpty ?? false) { + throw Exception( + 'Invalid DecisionTree content. Avoid blank lines inside ' + '... (use "#" YAML comment lines for ' + 'spacing) so CommonMark does not split the HTML block.', + ); + } + + final content = node.children?.map((n) => n.innerText).join('\n') ?? ''; + final data = loadYamlNode(content); + if (data is! YamlMap) { + throw ArgumentError( + 'Invalid DecisionTree content. Expected a YAML map with "steps" and ' + '"recommendations".', + ); + } + + final model = DecisionTreeModel.fromMap(data); + return DecisionTreeComponent(title: title, model: model); + } +} + +/// Renders an interactive decision tree from a [DecisionTreeModel]. +final class DecisionTreeComponent extends StatelessComponent { + const DecisionTreeComponent({super.key, required this.model, this.title}); + + final DecisionTreeModel model; + final String? title; + + @override + Component build(BuildContext context) { + return div( + classes: 'decision-tree', + attributes: {'role': 'region', 'aria-label': title ?? 'Decision tree'}, + [ + div(classes: 'decision-tree-toolbar', [ + div(classes: 'decision-tree-toolbar-title', [ + const MaterialIcon('account_tree'), + span([.text(title ?? 'Interactive decision guide')]), + ]), + div(classes: 'decision-tree-toolbar-actions', [ + button( + classes: 'text-button compact-button decision-tree-reset', + attributes: {'type': 'button', 'hidden': 'hidden'}, + [ + const MaterialIcon('restart_alt'), + span([.text('Start over')]), + ], + ), + button( + classes: + 'outlined-button compact-button decision-tree-toggle-all', + attributes: {'type': 'button', 'aria-pressed': 'false'}, + [ + const MaterialIcon('visibility'), + span(classes: 'decision-tree-toggle-label', [ + .text('Show all paths'), + ]), + ], + ), + ]), + ]), + div(classes: 'decision-tree-steps', [ + for (var i = 0; i < model.steps.length; i++) + _buildStep(model.steps[i], index: i), + ]), + div( + classes: 'decision-tree-recommendations', + attributes: {'aria-live': 'polite'}, + [for (final rec in model.recommendations) _buildRecommendation(rec)], + ), + ], + ); + } + + Component _buildStep(DecisionStep step, {required int index}) { + final isFirst = index == 0; + return div( + classes: ['decision-tree-step', if (isFirst) 'is-active'].toClasses, + attributes: { + 'data-step-id': step.id, + 'data-step-index': '$index', + if (!isFirst) 'hidden': 'hidden', + }, + [ + div(classes: 'decision-tree-step-header', [ + span(classes: 'decision-tree-step-badge', [ + .text('Question ${index + 1}'), + ]), + h4(classes: 'decision-tree-question no_toc', [ + DashMarkdown(content: step.question, inline: true), + ]), + ]), + if (step.description case final description?) + div(classes: 'decision-tree-step-description', [ + DashMarkdown(content: description), + ]), + if (step.checklist case final checklist? when checklist.isNotEmpty) + ul(classes: 'decision-tree-checklist', [ + for (final item in checklist) + li([DashMarkdown(content: item, inline: true)]), + ]), + if (step.callout case final callout?) _buildCallout(callout), + div( + classes: 'decision-tree-options', + attributes: {'role': 'group', 'aria-label': step.question}, + [ + for (final option in step.options) + _buildOption(stepId: step.id, option: option), + ], + ), + ], + ); + } + + Component _buildOption({ + required String stepId, + required DecisionOption option, + }) { + return button( + classes: 'decision-tree-option', + attributes: { + 'type': 'button', + 'aria-pressed': 'false', + 'data-from-step': stepId, + if (option.nextStep case final nextStep?) 'data-next-step': nextStep, + if (option.recommendation case final rec?) 'data-recommendation': rec, + }, + [ + div(classes: 'decision-tree-option-header', [ + if (option.icon case final iconId?) + MaterialIcon(iconId, classes: const ['decision-tree-option-icon']), + span(classes: 'decision-tree-option-label', [ + DashMarkdown(content: option.label, inline: true), + ]), + const MaterialIcon( + 'arrow_forward', + classes: ['decision-tree-option-arrow'], + ), + ]), + if (option.subtitle case final subtitle?) + span(classes: 'decision-tree-option-subtitle', [ + DashMarkdown(content: subtitle, inline: true), + ]), + ], + ); + } + + Component _buildRecommendation(DecisionRecommendation rec) { + return div( + classes: 'decision-tree-recommendation', + attributes: {'data-recommendation-id': rec.id, 'hidden': 'hidden'}, + [ + div(classes: 'decision-tree-recommendation-header', [ + MaterialIcon( + rec.icon ?? 'recommend', + classes: const ['decision-tree-recommendation-icon'], + ), + div(classes: 'decision-tree-recommendation-titles', [ + span(classes: 'decision-tree-recommendation-eyebrow', [ + .text('Recommended approach'), + ]), + h4(classes: 'decision-tree-recommendation-title no_toc', [ + DashMarkdown(content: rec.title, inline: true), + ]), + ]), + ]), + div(classes: 'decision-tree-recommendation-body', [ + DashMarkdown(content: rec.description), + ]), + if (rec.callout case final callout?) _buildCallout(callout), + if (rec.link case final link?) + div(classes: 'decision-tree-recommendation-actions', [ + Button( + href: link, + content: rec.linkText ?? 'Learn more', + style: ButtonStyle.filled, + trailingIcon: 'arrow_forward', + ), + ]), + ], + ); + } + + Component _buildCallout(DecisionCallout callout) { + return Component.element( + tag: 'aside', + classes: 'alert ${callout.cssClass} decision-tree-callout', + children: [ + if (callout.title case final title? when title.isNotEmpty) + div(classes: 'alert-header', [ + if (callout.iconId case final iconId?) MaterialIcon(iconId), + span([DashMarkdown(content: title, inline: true)]), + ]), + div(classes: 'alert-content', [DashMarkdown(content: callout.content)]), + ], + ); + } +} + +/// Data model parsed from `` YAML content. +final class DecisionTreeModel { + const DecisionTreeModel({required this.steps, required this.recommendations}); + + factory DecisionTreeModel.fromMap(Map map) { + final rawSteps = map['steps']; + if (rawSteps is! List || rawSteps.isEmpty) { + throw ArgumentError('DecisionTree requires a non-empty "steps" list.'); + } + + final rawRecommendations = map['recommendations']; + if (rawRecommendations is! List || rawRecommendations.isEmpty) { + throw ArgumentError( + 'DecisionTree requires a non-empty "recommendations" list.', + ); + } + + final steps = rawSteps + .map((s) => DecisionStep.fromMap(s as Map)) + .toList(growable: false); + final recommendations = rawRecommendations + .map((r) => DecisionRecommendation.fromMap(r as Map)) + .toList(growable: false); + + final stepIds = {for (final step in steps) step.id}; + final recIds = {for (final rec in recommendations) rec.id}; + + // Validate step option targets at build time so writers get immediate, + // actionable errors if a step or recommendation ID is mistyped. + for (final step in steps) { + for (final option in step.options) { + if (option.nextStep case final nextStep? + when !stepIds.contains(nextStep)) { + throw ArgumentError( + 'DecisionTree option "${option.label}" in step "${step.id}" ' + 'references unknown next_step "$nextStep". ' + 'Available steps: ${stepIds.join(", ")}.', + ); + } + if (option.recommendation case final rec? when !recIds.contains(rec)) { + throw ArgumentError( + 'DecisionTree option "${option.label}" in step "${step.id}" ' + 'references unknown recommendation "$rec". ' + 'Available recommendations: ${recIds.join(", ")}.', + ); + } + } + } + + return DecisionTreeModel(steps: steps, recommendations: recommendations); + } + + final List steps; + final List recommendations; +} + +/// A single question step in a [DecisionTreeModel]. +final class DecisionStep { + const DecisionStep({ + required this.id, + required this.question, + required this.options, + this.description, + this.checklist, + this.callout, + }); + + factory DecisionStep.fromMap(Map map) { + final id = map['id'] as String?; + final question = map['question'] as String?; + final rawOptions = map['options']; + + if (id == null || id.isEmpty) { + throw ArgumentError('Each DecisionTree step requires an "id".'); + } + if (question == null || question.isEmpty) { + throw ArgumentError('DecisionTree step "$id" requires a "question".'); + } + if (rawOptions is! List || rawOptions.isEmpty) { + throw ArgumentError( + 'DecisionTree step "$id" requires a non-empty "options" list.', + ); + } + + final rawChecklist = map['checklist']; + final checklist = rawChecklist is List + ? rawChecklist.map((item) => item.toString()).toList(growable: false) + : null; + + final rawCallout = map['callout']; + final callout = rawCallout is Map + ? DecisionCallout.fromMap(rawCallout) + : null; + + return DecisionStep( + id: id, + question: question, + description: map['description'] as String?, + checklist: checklist, + callout: callout, + options: rawOptions + .map((o) => DecisionOption.fromMap(o as Map)) + .toList(growable: false), + ); + } + + final String id; + final String question; + final String? description; + final List? checklist; + final DecisionCallout? callout; + final List options; +} + +/// A selectable option within a [DecisionStep]. +final class DecisionOption { + const DecisionOption({ + required this.label, + this.subtitle, + this.icon, + this.nextStep, + this.recommendation, + }); + + factory DecisionOption.fromMap(Map map) { + final label = map['label'] as String?; + if (label == null || label.isEmpty) { + throw ArgumentError('Each DecisionTree option requires a "label".'); + } + + final nextStep = map['next_step'] as String?; + final recommendation = map['recommendation'] as String?; + if ((nextStep == null) == (recommendation == null)) { + throw ArgumentError( + 'DecisionTree option "$label" must specify exactly one of ' + '"next_step" or "recommendation".', + ); + } + + return DecisionOption( + label: label, + subtitle: map['subtitle'] as String?, + icon: map['icon'] as String?, + nextStep: nextStep, + recommendation: recommendation, + ); + } + + final String label; + final String? subtitle; + final String? icon; + final String? nextStep; + final String? recommendation; +} + +/// A final outcome card in a [DecisionTreeModel]. +final class DecisionRecommendation { + const DecisionRecommendation({ + required this.id, + required this.title, + required this.description, + this.icon, + this.callout, + this.link, + this.linkText, + }); + + factory DecisionRecommendation.fromMap(Map map) { + final id = map['id'] as String?; + final title = map['title'] as String?; + final description = map['description'] as String?; + + if (id == null || id.isEmpty) { + throw ArgumentError('Each DecisionTree recommendation requires an "id".'); + } + if (title == null || title.isEmpty) { + throw ArgumentError( + 'DecisionTree recommendation "$id" requires a "title".', + ); + } + if (description == null || description.isEmpty) { + throw ArgumentError( + 'DecisionTree recommendation "$id" requires a "description".', + ); + } + + final rawCallout = map['callout']; + final callout = rawCallout is Map + ? DecisionCallout.fromMap(rawCallout) + : null; + + return DecisionRecommendation( + id: id, + title: title, + description: description, + icon: map['icon'] as String?, + callout: callout, + link: map['link'] as String?, + linkText: map['link_text'] as String?, + ); + } + + final String id; + final String title; + final String description; + final String? icon; + final DecisionCallout? callout; + final String? link; + final String? linkText; +} + +/// An optional alert callout attached to a [DecisionStep] or +/// [DecisionRecommendation]. +final class DecisionCallout { + const DecisionCallout({ + required this.type, + required this.content, + this.title, + }); + + factory DecisionCallout.fromMap(Map map) { + final type = (map['type'] as String? ?? 'note').toLowerCase(); + final content = map['content'] as String?; + if (content == null || content.isEmpty) { + throw ArgumentError('DecisionTree callout requires "content".'); + } + return DecisionCallout( + type: type, + title: map['title'] as String?, + content: content, + ); + } + + final String type; + final String? title; + final String content; + + String get cssClass => switch (type) { + 'important' => 'alert-important', + 'warning' || 'experimental' => 'alert-warning', + 'caution' => 'alert-error', + 'tip' || 'recommend' => 'alert-success', + 'secondary' => 'alert-secondary', + _ => 'alert-info', + }; + + String? get iconId => switch (type) { + 'important' => 'feedback', + 'warning' => 'warning', + 'experimental' => 'construction', + 'caution' => 'error', + 'tip' => 'lightbulb', + 'recommend' => 'bolt', + 'secondary' => null, + _ => 'info', + }; +} diff --git a/sites/docs/lib/_sass/_site.scss b/sites/docs/lib/_sass/_site.scss index df8a1d9de68..0a1af95ab49 100644 --- a/sites/docs/lib/_sass/_site.scss +++ b/sites/docs/lib/_sass/_site.scss @@ -37,6 +37,7 @@ @use 'package:site_shared/_sass/components/card'; @use 'package:site_shared/_sass/components/code'; @use 'package:site_shared/_sass/components/cookie-notice'; +@use 'package:site_shared/_sass/components/decision-tree'; @use 'package:site_shared/_sass/components/dropdown'; @use 'package:site_shared/_sass/components/ide-explorer'; @use 'package:site_shared/_sass/components/menu-toggle'; diff --git a/sites/docs/lib/main.server.dart b/sites/docs/lib/main.server.dart index 196bed0f2b2..3f618be8d7b 100644 --- a/sites/docs/lib/main.server.dart +++ b/sites/docs/lib/main.server.dart @@ -8,6 +8,7 @@ import 'package:jaspr_content/jaspr_content.dart'; import 'package:jaspr_content/theme.dart'; import 'package:path/path.dart' as path; import 'package:site_shared/components/common/card.dart'; +import 'package:site_shared/components/common/decision_tree.dart'; import 'package:site_shared/components/common/ide_explorer/markdown_component.dart'; import 'package:site_shared/components/common/material_icon.dart'; import 'package:site_shared/components/common/tabs.dart'; @@ -96,6 +97,7 @@ final RegExp _passThroughPattern = RegExp(r'.*\.(txt|json|pdf)$'); /// Custom "components" that can be used from Markdown files. List get _embeddableComponents => [ + const DecisionTree(), const DashTabs(), const DashImage(), const CodePreview(), diff --git a/sites/docs/lib/src/client/global_scripts.dart b/sites/docs/lib/src/client/global_scripts.dart index fc71855973a..f57b53833c7 100644 --- a/sites/docs/lib/src/client/global_scripts.dart +++ b/sites/docs/lib/src/client/global_scripts.dart @@ -21,6 +21,7 @@ void setUpSite() { _setUpToc(); _setUpSteppers(); _setUpIdeExplorers(); + _setUpDecisionTrees(); } void _setUpSearchKeybindings() { @@ -600,3 +601,184 @@ void _setUpIdeExplorer(web.Element explorer) { explorer.addEventListener('click', handleClick.toJS); } + +/// Set up interactivity of decision trees created with +/// the `` custom component. +void _setUpDecisionTrees() { + final trees = web.document.querySelectorAll('.decision-tree'); + for (var i = 0; i < trees.length; i++) { + _setUpDecisionTree(trees.item(i) as web.HTMLElement); + } +} + +void _setUpDecisionTree(web.HTMLElement tree) { + final steps = tree.querySelectorAll('.decision-tree-step'); + final recommendations = tree.querySelectorAll( + '.decision-tree-recommendation', + ); + final resetButton = + tree.querySelector('.decision-tree-reset') as web.HTMLElement?; + final toggleAllButton = + tree.querySelector('.decision-tree-toggle-all') as web.HTMLElement?; + final toggleLabel = tree.querySelector('.decision-tree-toggle-label'); + + void setHidden(web.Element element, bool isHidden) { + if (isHidden) { + element.setAttribute('hidden', 'hidden'); + } else { + element.removeAttribute('hidden'); + } + } + + void hideSubsequentFromStep(web.Element currentStep) { + var foundCurrent = false; + for (var i = 0; i < steps.length; i++) { + final step = steps.item(i) as web.HTMLElement; + if (step == currentStep) { + foundCurrent = true; + continue; + } + if (foundCurrent) { + setHidden(step, true); + step.classList.remove('is-active'); + final options = step.querySelectorAll('.decision-tree-option'); + for (var j = 0; j < options.length; j++) { + final opt = options.item(j) as web.HTMLElement; + opt.classList.remove('is-selected'); + opt.setAttribute('aria-pressed', 'false'); + } + } + } + + for (var i = 0; i < recommendations.length; i++) { + final rec = recommendations.item(i) as web.HTMLElement; + setHidden(rec, true); + rec.classList.remove('is-active'); + } + } + + void resetTree() { + tree.classList.remove('show-all'); + if (toggleAllButton != null) { + toggleAllButton.setAttribute('aria-pressed', 'false'); + } + if (toggleLabel != null) { + toggleLabel.textContent = 'Show all paths'; + } + + for (var i = 0; i < steps.length; i++) { + final step = steps.item(i) as web.HTMLElement; + final isFirst = i == 0; + setHidden(step, !isFirst); + step.classList.toggle('is-active', isFirst); + final options = step.querySelectorAll('.decision-tree-option'); + for (var j = 0; j < options.length; j++) { + final opt = options.item(j) as web.HTMLElement; + opt.classList.remove('is-selected'); + opt.setAttribute('aria-pressed', 'false'); + } + } + + for (var i = 0; i < recommendations.length; i++) { + final rec = recommendations.item(i) as web.HTMLElement; + setHidden(rec, true); + rec.classList.remove('is-active'); + } + + if (resetButton != null) { + setHidden(resetButton, true); + } + } + + void toggleShowAll() { + final showingAll = tree.classList.toggle('show-all'); + if (toggleAllButton != null) { + toggleAllButton.setAttribute('aria-pressed', '$showingAll'); + } + if (toggleLabel != null) { + toggleLabel.textContent = showingAll + ? 'Interactive mode' + : 'Show all paths'; + } + } + + void selectOption(web.HTMLElement option) { + final step = option.closest('.decision-tree-step'); + if (step == null) return; + + // Exit show-all mode if the user interacts with a specific option. + if (tree.classList.contains('show-all')) { + tree.classList.remove('show-all'); + if (toggleAllButton != null) { + toggleAllButton.setAttribute('aria-pressed', 'false'); + } + if (toggleLabel != null) { + toggleLabel.textContent = 'Show all paths'; + } + } + + final siblingOptions = step.querySelectorAll('.decision-tree-option'); + for (var i = 0; i < siblingOptions.length; i++) { + final sibling = siblingOptions.item(i) as web.HTMLElement; + final isSelected = sibling == option; + sibling.classList.toggle('is-selected', isSelected); + sibling.setAttribute('aria-pressed', '$isSelected'); + } + + hideSubsequentFromStep(step); + + if (resetButton != null) { + setHidden(resetButton, false); + } + + final nextStepId = option.getAttribute('data-next-step'); + if (nextStepId != null && nextStepId.isNotEmpty) { + final nextStep = tree.querySelector( + '.decision-tree-step[data-step-id="$nextStepId"]', + ) as web.HTMLElement?; + if (nextStep != null) { + setHidden(nextStep, false); + nextStep.classList.add('is-active'); + _scrollTo(nextStep, smooth: true); + } + return; + } + + final recId = option.getAttribute('data-recommendation'); + if (recId != null && recId.isNotEmpty) { + final rec = tree.querySelector( + '.decision-tree-recommendation[data-recommendation-id="$recId"]', + ) as web.HTMLElement?; + if (rec != null) { + setHidden(rec, false); + rec.classList.add('is-active'); + _scrollTo(rec, smooth: true); + } + } + } + + void handleClick(web.Event event) { + final target = event.target as web.Element?; + if (target == null) return; + + if (target.closest('.decision-tree-reset') != null) { + event.preventDefault(); + resetTree(); + return; + } + + if (target.closest('.decision-tree-toggle-all') != null) { + event.preventDefault(); + toggleShowAll(); + return; + } + + if (target.closest('.decision-tree-option') + case final web.HTMLElement option) { + event.preventDefault(); + selectOption(option); + } + } + + tree.addEventListener('click', handleClick.toJS); +} diff --git a/sites/docs/src/content/interop/index.md b/sites/docs/src/content/interop/index.md index ff3d3990b51..343c5a8ce4f 100644 --- a/sites/docs/src/content/interop/index.md +++ b/sites/docs/src/content/interop/index.md @@ -15,83 +15,123 @@ or read on to learn about the native interop layers. ## Choose your interop solution -```mermaid -flowchart TD - Start(["Question 1: What is your primary goal?"]) --> GoalA["Goal A: Platform integration
    (Swift, Kotlin, Java, Obj-C)"] - Start --> GoalB["Goal B: C-ABI wrapping
    (C, C++, Rust, Go)"] - Start --> GoalC["Goal C: Pure Dart package
    (No Flutter framework)"] - - GoalB --> FFIgen["Use FFIgen
    (or Flutter Rust Bridge)"]:::directNode - GoalC --> Direct["Use FFIgen or JNIgen"]:::directNode - - GoalA --> EvalPigeon{"Question 2: Do high-complexity
    constraints apply?"} - EvalPigeon -->|"No (most common)"| Pigeon["Use Pigeon
    (Recommended default)"]:::recommendedNode - EvalPigeon -->|"Yes (advanced)"| DirectInterop["Use FFIgen or JNIgen"]:::directNode - - classDef recommendedNode stroke:#1e8e3e,stroke-width:2px; - classDef directNode stroke:#1a73e8,stroke-width:2px; -``` - ---- - -Answer the following questions to walk through the decision tree in detail: - -### Question 1: What is your primary goal? - -**Goal A: Platform integration** -: Communicate with native OS features (Swift, Kotlin, Java, or Objective-C) - or expose an API surface. - Proceed to [Question 2: Do high-complexity constraints apply?](#question-2-do-high-complexity-constraints-apply). - -**Goal B: C-ABI wrapping** -: Wrap an existing native library (C, C++, Rust, or Go). - Use [FFIgen](/interop/ffigen) - (or [Flutter Rust Bridge][frb] for Rust). - -**Goal C: Pure Dart package** -: Build a Dart CLI tool without Flutter framework dependencies. - Use [FFIgen](/interop/ffigen) or - [JNIgen](/interop/jnigen). - -### Question 2: Do high-complexity constraints apply? - -If you're building a Flutter plugin, -first verify whether [Pigeon](/interop/pigeon) satisfies -your technical constraints. - -:::important -Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial -because the glue code lives in different languages. -Evaluate your plugin's constraints carefully before choosing between them. -::: - -Do any of the following requirements apply to your feature? - -- Your app processes high-frequency raw data (for example, video processing). -- Your integration requires advanced language bridging - (for example, cross-language subclasses, delegates, or proxies). -- Your feature requires manual memory management and multithreading over - guardrails. -- Your target language is C, C++, Rust, or Go. - -Based on whether any of the preceding requirements apply, -choose your interop tool: - -**No (most common) — Use [Pigeon](/interop/pigeon)** -: This is the default, recommended path. - It provides necessary guardrails, creates easily digestible interface - mappings, and is highly efficient for most API surface areas. - -**Yes — Use [FFIgen](/interop/ffigen) or [JNIgen](/interop/jnigen)** -: You need the flexibility, complexity, and performance of direct interop for - larger surface areas. - -:::note -Directly using FFIgen and JNIgen introduces potential pitfalls, -particularly around threading and memory management. -Pigeon is generally recommended because it has fewer potential pitfalls -and provides safer guardrails. -::: +Select your goals and constraints in the following interactive guide to find +the recommended interop tool for your project, or click **Show all paths** to +compare every option at once: + + +steps: + - id: primary-goal + question: What is your primary goal? + description: >- + Choose the option that best matches the native code you want to call + and how your Dart or Flutter project is structured. + options: + - label: "Goal A: Platform integration" + subtitle: >- + Communicate with native OS features (Swift, Kotlin, Java, or + Objective-C) or expose an API surface. + icon: devices + next_step: complexity-constraints + - label: "Goal B: C-ABI wrapping" + subtitle: >- + Wrap an existing native library written in C, C++, Rust, or Go. + icon: memory + recommendation: c-abi-wrapping + - label: "Goal C: Pure Dart package" + subtitle: >- + Build a Dart CLI tool or package without Flutter framework + dependencies. + icon: terminal + recommendation: pure-dart + # Step 2: Evaluate high-complexity constraints for platform integration. + - id: complexity-constraints + question: Do high-complexity constraints apply? + description: >- + If you're building a Flutter plugin, first verify whether + [Pigeon](/interop/pigeon) satisfies your technical constraints. + Do any of the following requirements apply to your feature? + checklist: + - >- + Your app processes high-frequency raw data + (for example, video processing). + - >- + Your integration requires advanced language bridging + (for example, cross-language subclasses, delegates, or proxies). + - >- + Your feature requires manual memory management and multithreading + over guardrails. + - Your target language is C, C++, Rust, or Go. + callout: + type: important + title: Important + content: >- + Migrating a plugin between Pigeon and FFIgen or JNIgen is non-trivial + because the glue code lives in different languages. Evaluate your + plugin's constraints carefully before choosing between them. + options: + - label: "No (most common)" + subtitle: >- + None of these high-complexity requirements apply to my plugin or app. + icon: verified + recommendation: pigeon + - label: "Yes (advanced)" + subtitle: >- + One or more of these requirements apply; I need direct native + bindings. + icon: tune + recommendation: direct-platform-interop +# Recommendations +recommendations: + - id: pigeon + title: Use Pigeon (Recommended default) + icon: recommend + description: >- + **[Pigeon](/interop/pigeon)** is the default, recommended path for + platform integration. It provides necessary guardrails, creates easily + digestible interface mappings, and is highly efficient for most API + surface areas. + link: /interop/pigeon + link_text: Learn about Pigeon + - id: direct-platform-interop + title: Use FFIgen or JNIgen + icon: bolt + description: >- + Use **[FFIgen](/interop/ffigen)** (for C, Objective-C, or Swift) or + **[JNIgen](/interop/jnigen)** (for Java or Kotlin) when you need the + flexibility, complexity, and performance of direct interop for larger + surface areas. + callout: + type: note + title: Note + content: >- + Directly using FFIgen and JNIgen introduces potential pitfalls, + particularly around threading and memory management. Pigeon is + generally recommended because it has fewer potential pitfalls and + provides safer guardrails. + link: /interop/ffigen + link_text: Explore FFIgen + - id: c-abi-wrapping + title: Use FFIgen (or Flutter Rust Bridge) + icon: memory + description: >- + Use **[FFIgen](/interop/ffigen)** to generate Dart FFI bindings for + existing C, C++, or Go libraries (or + **[Flutter Rust Bridge]({{site.pub-pkg}}/flutter_rust_bridge)** when + wrapping Rust libraries without `extern "C"`). + link: /interop/ffigen + link_text: Explore FFIgen + - id: pure-dart + title: Use FFIgen or JNIgen + icon: terminal + description: >- + For pure Dart packages and CLI tools without Flutter framework + dependencies, use **[FFIgen](/interop/ffigen)** (for C and Objective-C + libraries) or **[JNIgen](/interop/jnigen)** (for Java and Kotlin + libraries). + link: /interop/ffigen + link_text: Explore FFIgen + ## Package your interop code