Skip to content

[pigeon] Allow event channel unregistration - #13438

Merged
auto-submit[bot] merged 8 commits into
flutter:mainfrom
stuartmorgan:pigeon-event-channel-unregistration
Oct 30, 2026
Merged

auto-submit[bot] merged 8 commits into
flutter:mainfrom
stuartmorgan:pigeon-event-channel-unregistration

Conversation

@stuartmorgan

@stuartmorgan stuartmorgan commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds a new class for each event channel API that wraps the underlying EventChannel and allows setting the stream handler, matching the underlying Flutter API. This allows clients that need to set a new handler later (notably, removing a handler to avoid an engine-lifetime reference to a handler for a transient channel) to do so, using the same API that non-Pigeon event channel code would use.

The existing static register method is kept to avoid unnecessary breaking changes, but clients no longer need to use it if they want to create the channel directly.

Fixes flutter/flutter#195049

Pre-Review Checklist

Footnotes

  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. ↩ ↩2

Makes the handler parameter to event channel registration nullable, and
makes passing null/nil unregister the previously registered handler,
allowing clients to unergister handlers to prevent leaks in way that is
consistent with host API unregistration.

Fixes flutter/flutter#195049
@stuartmorgan
stuartmorgan marked this pull request as ready for review October 26, 2026 20:04

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for removing event channel handler registration in Kotlin and Swift by passing null to the stream handler registration method, updating the generators, generated code, and tests accordingly. The review feedback highlights that using static registeredChannels collections to track active channels in both Kotlin and Swift generators introduces severe memory leaks across FlutterEngine instances. The reviewer suggests directly unregistering the handlers via the binary messenger to avoid static state entirely.

Comment on lines 1476 to 1495
companion object {
fun register(messenger: BinaryMessenger, streamHandler: ${toUpperCamelCase(func.name)}StreamHandler, instanceName: String = "") {
private var registeredChannels = mutableMapOf<String, EventChannel>()

fun register(messenger: BinaryMessenger, streamHandler: ${toUpperCamelCase(func.name)}StreamHandler?, instanceName: String = "") {
var channelName: String = "${makeChannelName(api, func, dartPackageName)}"
if (instanceName.isNotEmpty()) {
channelName += ".\$instanceName"
}
val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodCodec).setStreamHandler(internalStreamHandler)
val previousChannel = registeredChannels.remove(channelName)
if (previousChannel != null) {
previousChannel.setStreamHandler(null)
}
if (streamHandler != null) {
val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
val channel = EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodCodec)
channel.setStreamHandler(internalStreamHandler)
registeredChannels[channelName] = channel
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a static registeredChannels map to keep track of active EventChannels introduces a severe memory leak. In Android, the JVM process (and thus static variables) can persist across multiple FlutterEngine instances (e.g., during activity recreation or in add-to-app scenarios). Because the static map retains references to EventChannel instances from previous engines, those engines and their associated resources will be leaked.

Instead of keeping a static map of channels, we can directly unregister the handler by calling messenger.setMessageHandler(channelName, null). This is because EventChannel.setStreamHandler(null) internally just calls messenger.setMessageHandler(name, null). This completely avoids the need for static state, is thread-safe, and prevents memory leaks.

Suggested change
companion object {
fun register(messenger: BinaryMessenger, streamHandler: ${toUpperCamelCase(func.name)}StreamHandler, instanceName: String = "") {
private var registeredChannels = mutableMapOf<String, EventChannel>()
fun register(messenger: BinaryMessenger, streamHandler: ${toUpperCamelCase(func.name)}StreamHandler?, instanceName: String = "") {
var channelName: String = "${makeChannelName(api, func, dartPackageName)}"
if (instanceName.isNotEmpty()) {
channelName += ".\$instanceName"
}
val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodCodec).setStreamHandler(internalStreamHandler)
val previousChannel = registeredChannels.remove(channelName)
if (previousChannel != null) {
previousChannel.setStreamHandler(null)
}
if (streamHandler != null) {
val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
val channel = EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodCodec)
channel.setStreamHandler(internalStreamHandler)
registeredChannels[channelName] = channel
}
}
}
companion object {
fun register(messenger: BinaryMessenger, streamHandler: ${toUpperCamelCase(func.name)}StreamHandler?, instanceName: String = "") {
var channelName: String = "${makeChannelName(api, func, dartPackageName)}"
if (instanceName.isNotEmpty()) {
channelName += ".\$instanceName"
}
if (streamHandler == null) {
messenger.setMessageHandler(channelName, null)
} else {
val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
val channel = EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodCodec)
channel.setStreamHandler(internalStreamHandler)
}
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those engines and their associated resources will be leaked.

I'm skeptical that this leaks the entire engine (if it does, the Android engine should get a weak proxy object like iOS got), but will will potentially accumulate channels from plugins that don't do cleanup, which isn't ideal.

More importantly, I forgot to consider multi-engine cases in keying the dictionary, so registering the same plugin on multiple engines will actually stomp the connection.

I can fix the latter by changing the dictionary, but maybe it's better to return an opaque wrapper around the event channel instead, and let that be used for cleanup. It makes the unregistration API different, but it means that nothing can outlive the combination of engine+plugin since there won't be an external reference.

Instead of keeping a static map of channels, we can directly unregister the handler by calling messenger.setMessageHandler(channelName, null). This is because EventChannel.setStreamHandler(null) internally just calls messenger.setMessageHandler(name, null). This completely avoids the need for static state, is thread-safe, and prevents memory leaks.

That is currently how it works, but we shouldn't rely on it. Exhibit A, the iOS behavior, and my comment about that it the issue.

Comment on lines 2674 to 2693
class ${toUpperCamelCase(func.name)}StreamHandler: PigeonEventChannelWrapper<${_swiftTypeForDartType(func.returnType)}> {
private static var registeredChannels: [String: FlutterEventChannel] = [:]
static func register(with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler) {
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler?) {
var channelName = "${makeChannelName(api, func, dartPackageName)}"
if !instanceName.isEmpty {
channelName += ".\\(instanceName)"
}
let internalStreamHandler = PigeonStreamHandler<${_swiftTypeForDartType(func.returnType)}>(wrapper: streamHandler)
let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: ${_getMethodCodecVarName(generatorOptions)})
channel.setStreamHandler(internalStreamHandler)
if let previousChannel = registeredChannels.removeValue(forKey: channelName) {
previousChannel.setStreamHandler(nil)
}
if let streamHandler = streamHandler {
let internalStreamHandler = PigeonStreamHandler<${_swiftTypeForDartType(func.returnType)}>(wrapper: streamHandler)
let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: ${_getMethodCodecVarName(generatorOptions)})
channel.setStreamHandler(internalStreamHandler)
registeredChannels[channelName] = channel
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a static registeredChannels dictionary to keep track of active FlutterEventChannels introduces a severe memory leak. In iOS, static variables persist as long as the app process is alive. If the FlutterEngine is destroyed and recreated, the static dictionary will retain references to FlutterEventChannel instances from the previous engine, leaking the entire engine and its resources.

Instead of keeping a static dictionary of channels, we can directly unregister the handler by calling messenger.setMessageHandlerOnChannel(channelName, binaryMessageHandler: nil). This is because FlutterEventChannel.setStreamHandler(nil) internally just calls [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil]. This completely avoids the need for static state, is thread-safe, and prevents memory leaks.

Suggested change
class ${toUpperCamelCase(func.name)}StreamHandler: PigeonEventChannelWrapper<${_swiftTypeForDartType(func.returnType)}> {
private static var registeredChannels: [String: FlutterEventChannel] = [:]
static func register(with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler) {
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler?) {
var channelName = "${makeChannelName(api, func, dartPackageName)}"
if !instanceName.isEmpty {
channelName += ".\\(instanceName)"
}
let internalStreamHandler = PigeonStreamHandler<${_swiftTypeForDartType(func.returnType)}>(wrapper: streamHandler)
let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: ${_getMethodCodecVarName(generatorOptions)})
channel.setStreamHandler(internalStreamHandler)
if let previousChannel = registeredChannels.removeValue(forKey: channelName) {
previousChannel.setStreamHandler(nil)
}
if let streamHandler = streamHandler {
let internalStreamHandler = PigeonStreamHandler<${_swiftTypeForDartType(func.returnType)}>(wrapper: streamHandler)
let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: ${_getMethodCodecVarName(generatorOptions)})
channel.setStreamHandler(internalStreamHandler)
registeredChannels[channelName] = channel
}
}
}
class ${toUpperCamelCase(func.name)}StreamHandler: PigeonEventChannelWrapper<${_swiftTypeForDartType(func.returnType)}> {
static func register(with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler?) {
var channelName = "${makeChannelName(api, func, dartPackageName)}"
if !instanceName.isEmpty {
channelName += ".\\(instanceName)"
}
if streamHandler == nil {
messenger.setMessageHandlerOnChannel(channelName, binaryMessageHandler: nil)
} else {
let internalStreamHandler = PigeonStreamHandler<${_swiftTypeForDartType(func.returnType)}>(wrapper: streamHandler)
let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: ${_getMethodCodecVarName(generatorOptions)})
channel.setStreamHandler(internalStreamHandler)
}
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because FlutterEventChannel.setStreamHandler(nil) internally just calls [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil].

This is a lie, which is why the PR didn't do that.

@stuartmorgan
stuartmorgan marked this pull request as draft October 26, 2026 22:50
@stuartmorgan
stuartmorgan marked this pull request as ready for review October 28, 2026 18:46

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates Pigeon to support removing event channel handler registrations in Kotlin and Swift by passing null to the stream handler registration method. It introduces new event channel wrapper classes in both Kotlin and Swift generators, and updates the register method to return these channel instances. Redundant return statements and open modifiers are also cleaned up in the Kotlin generator. The review feedback suggests adding the @discardableResult attribute to the generated Swift register method to prevent compiler warnings when callers ignore the returned channel instance.

Comment on lines 2703 to 2711
static func register(
with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: $streamHandlerClassName
) -> $channelClassName {
let channel = $channelClassName(messenger: messenger, instanceName: instanceName)
channel.setStreamHandler(streamHandler)
return channel
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Swift, if a function returns a value and the caller does not use it, the compiler will emit an unused result warning unless the function is annotated with @discardableResult. Since the register method's return value can be safely ignored (as documented in its doc comment), we should add @discardableResult to prevent compiler warnings in existing codebases that upgrade to this version of Pigeon without capturing the returned channel.

Suggested change
static func register(
with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: $streamHandlerClassName
) -> $channelClassName {
let channel = $channelClassName(messenger: messenger, instanceName: instanceName)
channel.setStreamHandler(streamHandler)
return channel
}
@discardableResult
static func register(
with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: $streamHandlerClassName
) -> $channelClassName {
let channel = $channelClassName(messenger: messenger, instanceName: instanceName)
channel.setStreamHandler(streamHandler)
return channel
}

@stuartmorgan

Copy link
Copy Markdown
Contributor Author

Design notes:

  • Because the underlying API on iOS requires keeping the original EventChannel around to correctly unregister, I couldn't make this work in a way that wasn't stateful for clients without causing lifetime extensions for multi-engine cases (see comments in the Gemini review above).
  • My next thought was to make a single opaque, generic PigeonEventChannelRegistration object that internally stored the EventChannel and provide an unregister static method that took that registration (similar to some iOS/macOS SDK observer API patterns). However, that ran into the PigeonError problem of collisions if there were multiple files. I could have done the filename-based approach, but that felt clunky for a public opaque object so I decided to just factor our the channel and provide an API that more closely mirrors the non-Pigeon Flutter API. That avoids naming issues, and also makes the pattern familiar to anyone who has used the base event channel APIs.

@stuartmorgan

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for removing event channel handler registration by passing null to the stream handler registration method in Pigeon. It updates the Kotlin and Swift generators to create a typed channel class for event streams, allowing for more flexible handler management. Additionally, it includes updates to the test plugins to exercise the new handler removal functionality. I have provided feedback regarding the use of @discardableResult in Swift to maintain backward compatibility and improve code cleanliness in the test suite.

indent.addScoped('{', '}', () {
if (customType.type == CustomTypes.customClass) {
indent.write('return (readValue(buffer) as? List<Any?>)?.let ');
indent.write('(readValue(buffer) as? List<Any?>)?.let ');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the same change below are opportunistic cleanup of warnings I noticed in the IDE while prototyping solutions.

}

interface ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper<T> {
open fun onListen(p0: Any?, sink: PigeonEventSink<T>) {}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with these (open is the default for interfaces).

@tarrinneal tarrinneal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I didn't do this when I put this all together.

Comment thread packages/pigeon/CHANGELOG.md Outdated
Comment on lines +3 to +4
* [kotlin,swift] Adds support for removing event channel handler registration
by passing `null` to the stream handler registration method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It looks like this wording is from the earlier iteration where register accepted null—consider updating it to mention the new *EventChannel class and setStreamHandler(null).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I rewrote the doc comments but forgot about the changelog. Fixed.

Comment on lines +64 to 75
// Store one handler to exercise the handler removal codepath, and discard
// the other to test that the return value can be safely ignored.
let streamHandlerOne = SendConsistentNumbers(numberToSend: 1)
let channelOne = StreamConsistentNumbersStreamHandler.register(
with: binaryMessenger,
instanceName: "1",
streamHandler: streamHandlerOne)
streamHandlerOne.onCompleted = { channelOne.setStreamHandler(nil) }
StreamConsistentNumbersStreamHandler.register(
with: binaryMessenger, instanceName: "1",
streamHandler: SendConsistentNumbers(numberToSend: 1))
StreamConsistentNumbersStreamHandler.register(
with: binaryMessenger, instanceName: "2",
with: binaryMessenger,
instanceName: "2",
streamHandler: SendConsistentNumbers(numberToSend: 2))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TestPlugin.swift, channelOne.setStreamHandler(nil) runs in onCompleted after the 10th event, whereas TestPlugin.kt only calls eventChannelOne?.setStreamHandler(null) in onDetachedFromEngine, and integration_tests.dart finishes after the first stream completes. Would it be worth also unregistering on completion in TestPlugin.kt and asserting in integration_tests.dart that listening to instance "1" a second time fails (while instance "2" still succeeds)?

@stuartmorgan stuartmorgan Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the unregistration to match Swift. I wrote the Kotlin version first, but there's no corresponding detach call on the iOS side in the plugin currently which is why it was different in the first place. It's easy enough to have Kotlin match Swift so that the tests are easier to understand as a whole.

I don't think re-listening on the Dart side would fail; IIRC listening to a channel without a native handler just gives an empty stream. This is such a trivial passthrough now that I don't think we need to specifically test the behavior, since it would be very hard to regress without breaking compilation.

@stuartmorgan stuartmorgan added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 30, 2026
@auto-submit
auto-submit Bot merged commit 3742a4e into flutter:main Oct 30, 2026
13 checks passed
flutter-zl pushed a commit to flutter-zl/flutter that referenced this pull request Oct 30, 2026
flutter/packages@0b72e92...3742a4e

2026-10-30 [email protected] [pigeon] Allow event channel
unregistration (flutter/packages#13438)
2026-10-30 [email protected] [material_ui] Fix Escape key not
closing MenuAnchor opened by tap or click (flutter/packages#13430)
2026-10-30 [email protected] manual roll and manual build and copy
(flutter/packages#13495)
2026-10-29 [email protected] [pigeon] Separate native interop Dart
bindings into a conditionally imported companion file
(flutter/packages#13393)
2026-10-29 [email protected] [image_picker_ios] PR 4/5 Migrate
the view provider, PHPicker save operation, and image utilities to Swift
(flutter/packages#13394)
2026-10-29 [email protected] Sync release-material_ui-1.9.0 to
main (flutter/packages#13479)
2026-10-29 [email protected] [material_ui]
Unskip web tests that pass on CanvasKit and Skwasm: time_picker_test
(flutter/packages#13471)
2026-10-29 [email protected] [vector_graphics]
Dispose pictures decoded after their VectorGraphic is removed
(flutter/packages#13435)
2026-10-29 [email protected]
[camera_android_x] implement CameraX RecordingStats and VideoRecordEvent
APIs (flutter/packages#13330)
2026-10-29 [email protected] Sync release-go_router-18.1.2 to
main (flutter/packages#13480)
2026-10-29 [email protected] Sync release-cupertino_ui-1.1.5 to
main (flutter/packages#13481)
2026-10-29 [email protected]
[vector_graphics_compiler] Fix em units in text positions
(flutter/packages#13388)

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 2, 2026
…13528)

**This PR fixes the tree.** `Linux_android custom_package_tests master` is failing on `main` (all 3 attempts of [#4084](https://ci.chromium.org/p/flutter/builders/prod/Linux_android%20custom_package_tests%20master/4084)) in the Pigeon integration test `event channels handle multiple instances`:

```
The following MissingPluginException was thrown while de-activating platform stream on channel
dev.flutter.pigeon.pigeon_integration_tests.EventChannelMethods.streamConsistentNumbers.1:
MissingPluginException(No implementation found for method cancel on channel
dev.flutter.pigeon.pigeon_integration_tests.EventChannelMethods.streamConsistentNumbers.1)
```

### Cause

The test plugin unregisters the stream handler for instance `"1"` (to exercise event channel unregistration, added in #13438) immediately after calling `endOfStream()`. On the Dart side, the end of the stream closes the `EventChannel`'s broadcast stream, which then sends a `cancel` method call back on the same channel. If the handler has already been removed by the time that `cancel` call arrives, it fails with a `MissingPluginException`, which fails the test. Whether it fails depends on timing, so it shows up as a flake.

### Fix

Unregister the handler from the stream handler's `onCancel` instead, i.e. only after the `cancel` call has been received, in both the Kotlin (Android) and Swift (iOS/macOS) test plugins. The generated Pigeon stream handler wrappers already forward the platform `onCancel` to the user handler, so no generator or generated-code changes are needed. The test still exercises unregistration; it just no longer races with the channel teardown.

This only changes test plugin code under `platform_tests/`, so there is no change to the published package.

### Testing

On an Android emulator (Pixel 9a), running only `event channels handle multiple instances`:

| | Runs | Failures (`MissingPluginException` on `cancel`) |
|---|---|---|
| Without this fix (`main`) | 5 | 2 |
| With this fix | 10 | 0 |

The full `android_kotlin_integration_tests` suite (680 tests) also passes with this fix, as do `dart_unittests` and `flutter_unittests`. The Swift change mirrors the Kotlin one and is covered by the iOS/macOS integration tests in CI.

### Related

flutter/flutter#179154 tracks a different flake in the same test on macOS Swift (`Bad state: Cannot add new events after calling close`, i.e. an event sent after `endOfStream`). This PR does not address that one, and the macOS Swift skip is left in place.

## Pre-Review Checklist

[^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.
gmackall pushed a commit to gmackall/flutter that referenced this pull request Nov 3, 2026
flutter/packages@0b72e92...3742a4e

2026-10-30 [email protected] [pigeon] Allow event channel
unregistration (flutter/packages#13438)
2026-10-30 [email protected] [material_ui] Fix Escape key not
closing MenuAnchor opened by tap or click (flutter/packages#13430)
2026-10-30 [email protected] manual roll and manual build and copy
(flutter/packages#13495)
2026-10-29 [email protected] [pigeon] Separate native interop Dart
bindings into a conditionally imported companion file
(flutter/packages#13393)
2026-10-29 [email protected] [image_picker_ios] PR 4/5 Migrate
the view provider, PHPicker save operation, and image utilities to Swift
(flutter/packages#13394)
2026-10-29 [email protected] Sync release-material_ui-1.9.0 to
main (flutter/packages#13479)
2026-10-29 [email protected] [material_ui]
Unskip web tests that pass on CanvasKit and Skwasm: time_picker_test
(flutter/packages#13471)
2026-10-29 [email protected] [vector_graphics]
Dispose pictures decoded after their VectorGraphic is removed
(flutter/packages#13435)
2026-10-29 [email protected]
[camera_android_x] implement CameraX RecordingStats and VideoRecordEvent
APIs (flutter/packages#13330)
2026-10-29 [email protected] Sync release-go_router-18.1.2 to
main (flutter/packages#13480)
2026-10-29 [email protected] Sync release-cupertino_ui-1.1.5 to
main (flutter/packages#13481)
2026-10-29 [email protected]
[vector_graphics_compiler] Fix em units in text positions
(flutter/packages#13388)

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD p: pigeon platform-android platform-ios platform-macos

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[pigeon] Provide a way to remove event channel handlers

2 participants