Repository navigation
[pigeon] Fix standalone Native Interop @FlutterApi issues - #13609
Merged
Merged
Conversation
- Prefix internal _<Api>Registrar members with pigeonVar_ so FlutterApi methods named register or dartApi no longer collide with JNI. - Make <Api>.implement fall back to the default instance name when name is empty.
Contributor
There was a problem hiding this comment.
Code Review
This pull request updates Pigeon to version 29.1.2. It resolves name collisions in the generated Dart code for @FlutterApi when using JNI by prefixing internal registrar members with varNamePrefix, removing dartApi and register from the reserved names list. It also ensures that @FlutterApi implement registrations default to defaultInstanceName instead of an empty string. Additionally, the objective_c dependency is restricted to <9.6.3 across several packages due to an iOS simulator issue. There are no review comments, and I have no feedback to provide.
Merged
11 tasks done
rmacnak-google
pushed a commit
to rmacnak-google/flutter
that referenced
this pull request
Nov 10, 2026
…r#196296) flutter/packages@565c657...08faf64 2026-11-10 [email protected] [pigeon] Pass data classes and collections in a single buffer per native interop call (flutter/packages#13265) 2026-11-10 [email protected] [ci] Fix release workflows (flutter/packages#13611) 2026-11-09 [email protected] [pigeon] Fix standalone Native Interop @FlutterApi issues (flutter/packages#13609) 2026-11-09 [email protected] [cross_file_web] Adds support to instantiate the web PlatformXFile without needing to import web apis (flutter/packages#13395) 2026-11-09 [email protected] Sync release-go_router-18.1.4 to main (flutter/packages#13606) 2026-11-09 [email protected] Sync release-material_ui-1.11.0 to main (flutter/packages#13605) 2026-11-09 [email protected] [vector_graphics_compiler] Resolve Flutter artifact cache via package:cli_util (flutter/packages#13252) 2026-11-09 [email protected] [ci] Remove `pull_request_target` workflows (flutter/packages#13581) 2026-11-09 [email protected] Roll Flutter from acade0f to d463821 (1 revision) (flutter/packages#13601) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit Bot
pushed a commit
that referenced
this pull request
Nov 10, 2026
…e-aware unregistration (#13576) Associates Native Interop (`useJni` / `useFfi`) `@HostApi` and `@FlutterApi` registrations with the Flutter engine that made them. Lookups route to the calling engine's registration when the engine is known, and detaching one engine removes only that engine's registration. Previously, attaching a secondary `FlutterEngine` overwrote any existing registration under the same `name`. Detaching that engine via `register(null)` then cleared the entry for all remaining engines in the process. For example, `shared_preferences_android` failed in the main engine after a background engine detached (flutter/flutter#194920). > [!NOTE] > Per review, the standalone `@FlutterApi` fixes (the `pigeonVar_` prefix for internal `_<Api>Registrar` members so `register` / `dartApi` no longer collide on JNI, `implement` with an empty `name`, and the `objective_c <9.6.3` test pin) are split out into #13609 so they can land on 29.x first. This PR contains only the unregistration and multi-engine changes, which depend on each other. ### Engine identity without deprecated APIs or new stable requirements `register` / `unregister` now take the engine handle that plugins and apps already have. Pigeon derives an engine key from it, so moving to `engineId` once flutter/flutter#195881 reaches `stable` is an internal change, not another breaking change: | Caller | Engine key | |---|---| | Kotlin app: `MyApiRegistrar().register(api, flutterEngine)` | `FlutterEngine.engineId` (public on stable; exact) | | Kotlin plugin: `MyApiRegistrar().register(api, binding)` | `binding.getEngineId()` when the embedding provides a non-zero value (#195881, detected at runtime, survives R8). Otherwise a per-engine key from `binding.binaryMessenger`. | | Swift: `MyApiSetup.register(api:registrar:)` | `registrar.engineIdentifier` when available and non-zero (detected at runtime). Otherwise a per-engine key from the registrar's messenger. | Generated code never calls deprecated embedding APIs such as `FlutterPluginBinding.getFlutterEngine()`. An engine ID of `0` is treated as unknown, because real IDs are never `0`. `0` shows up for registrars whose engine is already deallocating, and for unstubbed test doubles, and must not be shared between engines. Dart lookups (`createWithNativeInteropApi()` / `<HostApi>ForNativeInterop.getInstance()`) choose a registration in this order: 1. The one whose key matches `PlatformDispatcher.instance.engineId`. 2. Otherwise, if the isolate has no engine ID, one from the engine this isolate first resolved a handle from. This is tracked per generated Pigeon file. 3. Otherwise, the most recent registration. Native `@FlutterApi` lookups use the same keys through `<FlutterApi>Registrar` `getInstance(binding | flutterEngine)` in Kotlin and `getInstance(registrar:)` in Swift. **Exact vs. best-effort routing.** Routing is exact whenever native code knows the engine identifier: Kotlin `register(api, flutterEngine)` always, and plugin bindings / registrars on Flutter versions that expose it. Otherwise it is best-effort. It is correct when each engine's Dart resolves its first handle from each Pigeon file at startup, but it can pick another engine's registration when: - Engines are created back-to-back before their Dart starts. - A file's first lookup happens lazily while another engine is attached. - Lookups come from `Isolate.spawn` / `Isolate.run` isolates. macOS plugins receive no detach callback, so macOS registrations currently outlive their engine. The README and guides document these limits and recommend platform channels / `@ProxyApi` for engine-stateful APIs in those cases. Full engine routing remains tracked in flutter/flutter#195877. ### Summary of Changes * **Engine-keyed registration stacks & `unregister`**: Each `name` keeps an ordered stack of `(engine key, instance)` entries. * Re-registering the same instance for the same engine moves it to the top and keeps its existing wrapper. * `unregister(api, engine)` removes only that engine's entry, even out of order. If the engine key can't be resolved, it falls back to matching by instance identity, but only when a single engine holds that instance. * **Detached `@HostApi` safety**: Calls through a handle whose instance was unregistered throw `unregistered-instance` (Kotlin/Swift) instead of crashing. Swift `Setup` wrappers use `resolveApi() throws` instead of `api!`. * **Engine teardown safety (Swift)**: Engine keys are cached per registrar. `unregister` from `detachFromEngine(for:)` therefore works while the engine is deallocating and its messenger is already gone. * **Registry thread safety**: Native Interop calls from background isolates run on worker threads. Registration maps, wrapper API references, and engine-key caches are therefore guarded internally: * Kotlin uses `synchronized` / `@Synchronized` and `@Volatile`. * Swift uses `NSLock` / `NSRecursiveLock`. Removed entries are released after unlocking, so their `deinit` doesn't run while the lock is held. * This doesn't make registration a thread-safe API contract. Registration is still expected on the main thread. * **Dart `@FlutterApi`**: * `<FlutterApi>.setUp(null)` deregisters only the current isolate's native callback. * Registering via `setUp(a)` or the registrar, then calling `setUp(null)`, now correctly removes `a`. * Adds `<FlutterApi>Registrar.unregister`. * Registrations are tagged with the isolate's engine so native lookups reach the right isolate. * **Documentation**: * Updates the README, Native Interop guide, migration guide, migration skill, and `CHANGELOG.md`, including the routing guarantees and limits above. * Notes that `registrar.publish(instance)` is required for iOS to call `detachFromEngine(for:)`. * **Tests**: * Generator unit tests for Dart, Kotlin, and Swift. * Kotlin `NativeInteropRegistrarTest`, iOS `RunnerTests`, and macOS `BasicCompileTest`. These cover: * the same API on two engines * out-of-order unregister * the identity fallback * resolve order * `@FlutterApi` lookup by engine * Real secondary-engine and multi-isolate integration tests. * An integration test asserting that registrations use the exact `PlatformDispatcher.instance.engineId` when the platform exposes it. It runs in minified Android release builds to cover R8. ### Breaking Native Interop API Changes (`30.0.0`) * **Engine-aware registration**: * `@HostApi` `register` / `unregister` take the engine: `MyApiRegistrar().register(api, binding)` / `register(api, flutterEngine)` in Kotlin, and `MyApiSetup.register(api:registrar:)` in Swift. `unregister` follows the same shape. * Native `@FlutterApi` lookup takes the engine: `getInstance(binding | flutterEngine)` in Kotlin and `getInstance(registrar:)` in Swift. The name-only native `getInstance` is removed. * **Explicit deregistration**: * Replaces `register(null)` / `register(api: nil)` / `registerInstance(null | nil)` with `unregister` / `unregisterInstance`, which remove only the given instance for the given engine. * `register` / `registerInstance` require a non-null API and return `void` / `Unit` / `Void`. * **Swift `@HostApi` `AnyObject` constraint**: FFI Swift `@HostApi` protocols are constrained to `AnyObject` so instances can be matched by identity. * **Reserved names**: `pigeonResolveInstance`, `pigeonEngineKey`, and `pigeonEngineKeyOf` are reserved as `@HostApi` method names. Part of flutter/flutter#195877 Part of flutter/flutter#194920 ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out from #13576 per review. These are non-breaking fixes that can land on 29.x ahead of the 30.0.0 multi-engine changes.
_<Api>Registrarmembers (dartApi,register) withpigeonVar_. This means@FlutterApimethods namedregisterordartApino longer collide with the JNIgen interface whenuseJniis enabled, so those names are no longer reserved.<FlutterApi>.implementnow defaultsnameto the default instance name and treats an emptynameas the default. Previously it registered under'', which lookups never matched.objective_cto<9.6.3in the test packages and the native interop example app, to work around a debug-mode object validity check bug on the iOS simulator ([objective_c] FixisValidObjectbug on iOS simulator dart-lang/native#3788).work towards flutter/flutter#195877
Pre-Review Checklist
[shared_preferences]///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2