[pigeon] Add usage docs to generated Dart event channel methods (#11894)
The generated event-channel method creates a new `EventChannel` each time it is called, but nothing in the generated code documented that — making it easy to call repeatedly for the same instance, or to assume the returned stream is single-subscription. This implements the "definitely" part of the linked issue:
> Definitely add docs to the generated code, indicated both that it shouldn't be called multiple times for the same instance, and that the returned stream is a broadcast stream.
Each generated event-channel method now gets a usage doc comment:
```dart
/// Returns a broadcast [Stream] of events from the `streamEvents` event channel.
///
/// Each call to this method creates a new [EventChannel], so it should
/// not be called multiple times for the same `instanceName`. To deliver
/// events to multiple listeners, call this method once and listen to the
/// returned broadcast stream multiple times instead.
```
While there, this also emits the user's own documentation comments on event channel methods — previously only the API-level comments were emitted and per-method comments were silently dropped. The two are combined via the existing `generatorComments` mechanism in `addDocumentationComments`, which already handles user-comment/generator-comment separation.
The wrapper-object and renaming ideas from the issue are intentionally not addressed here, per the discussion — those are design decisions; this PR is scoped to the documentation gap.
Checked-in generated files containing event channels (`example/app` and `shared_test_plugin_code`) are regenerated. The new generator test verifies both the user comments and the usage docs are emitted directly above the generated method, and fails without the generator change.
Fixes https://github.com/flutter/flutter/issues/177776
## Pre-Review Checklist
Note: the multi-language auto-formatter step of `tool/generate.dart` could not run fully on this machine (missing clang-format and related binaries); only Dart outputs changed, and those were formatted with `dart format`. If CI's format check flags anything, I'll address it.
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index a87e4e9..803f62b 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 27.1.1
+
+* [dart] Adds usage documentation to generated event channel methods, and
+ emits the user's documentation comments on them.
+
## 27.1.0
* [swift] Adds `CaseIterable` conformance to generated enums.
diff --git a/packages/pigeon/example/app/lib/src/event_channel_messages.g.dart b/packages/pigeon/example/app/lib/src/event_channel_messages.g.dart
index 28217b3..d13b926 100644
--- a/packages/pigeon/example/app/lib/src/event_channel_messages.g.dart
+++ b/packages/pigeon/example/app/lib/src/event_channel_messages.g.dart
@@ -188,6 +188,12 @@
const StandardMethodCodec pigeonMethodCodec = StandardMethodCodec(_PigeonCodec());
+/// Returns a broadcast [Stream] of events from the `streamEvents` event channel.
+///
+/// Each call to this method creates a new [EventChannel], so it should
+/// not be called multiple times for the same `instanceName`. To deliver
+/// events to multiple listeners, call this method once and listen to the
+/// returned broadcast stream multiple times instead.
Stream<PlatformEvent> streamEvents({String instanceName = ''}) {
if (instanceName.isNotEmpty) {
instanceName = '.$instanceName';
diff --git a/packages/pigeon/lib/src/dart/dart_generator.dart b/packages/pigeon/lib/src/dart/dart_generator.dart
index ae0614a..3218103 100644
--- a/packages/pigeon/lib/src/dart/dart_generator.dart
+++ b/packages/pigeon/lib/src/dart/dart_generator.dart
@@ -632,6 +632,19 @@
indent.newln();
addDocumentationComments(indent, api.documentationComments, docCommentSpec);
for (final Method func in api.methods) {
+ addDocumentationComments(
+ indent,
+ func.documentationComments,
+ docCommentSpec,
+ generatorComments: <String>[
+ 'Returns a broadcast [Stream] of events from the `${func.name}` event channel.',
+ '',
+ 'Each call to this method creates a new [EventChannel], so it should',
+ 'not be called multiple times for the same `instanceName`. To deliver',
+ 'events to multiple listeners, call this method once and listen to the',
+ 'returned broadcast stream multiple times instead.',
+ ],
+ );
indent.format('''
Stream<${func.returnType.baseName}> ${func.name}(${_getMethodParameterSignature(func.parameters, addTrailingComma: true)} {String instanceName = ''}) {
if (instanceName.isNotEmpty) {
diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart
index 4cc20cf..dbc56d4 100644
--- a/packages/pigeon/lib/src/generator_tools.dart
+++ b/packages/pigeon/lib/src/generator_tools.dart
@@ -15,7 +15,7 @@
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
-const String pigeonVersion = '27.1.0';
+const String pigeonVersion = '27.1.1';
/// Default plugin package name.
const String defaultPluginPackageName = 'dev.flutter.pigeon';
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart
index 48d008b..9602837 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart
@@ -663,6 +663,12 @@
const StandardMethodCodec pigeonMethodCodec = StandardMethodCodec(_PigeonCodec());
+/// Returns a broadcast [Stream] of events from the `streamInts` event channel.
+///
+/// Each call to this method creates a new [EventChannel], so it should
+/// not be called multiple times for the same `instanceName`. To deliver
+/// events to multiple listeners, call this method once and listen to the
+/// returned broadcast stream multiple times instead.
Stream<int> streamInts({String instanceName = ''}) {
if (instanceName.isNotEmpty) {
instanceName = '.$instanceName';
@@ -676,6 +682,12 @@
});
}
+/// Returns a broadcast [Stream] of events from the `streamEvents` event channel.
+///
+/// Each call to this method creates a new [EventChannel], so it should
+/// not be called multiple times for the same `instanceName`. To deliver
+/// events to multiple listeners, call this method once and listen to the
+/// returned broadcast stream multiple times instead.
Stream<PlatformEvent> streamEvents({String instanceName = ''}) {
if (instanceName.isNotEmpty) {
instanceName = '.$instanceName';
@@ -689,6 +701,12 @@
});
}
+/// Returns a broadcast [Stream] of events from the `streamConsistentNumbers` event channel.
+///
+/// Each call to this method creates a new [EventChannel], so it should
+/// not be called multiple times for the same `instanceName`. To deliver
+/// events to multiple listeners, call this method once and listen to the
+/// returned broadcast stream multiple times instead.
Stream<int> streamConsistentNumbers({String instanceName = ''}) {
if (instanceName.isNotEmpty) {
instanceName = '.$instanceName';
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_without_classes_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_without_classes_tests.gen.dart
index 1bc0680..d6c39f9 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_without_classes_tests.gen.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_without_classes_tests.gen.dart
@@ -36,6 +36,12 @@
const StandardMethodCodec pigeonMethodCodec = StandardMethodCodec(_PigeonCodec());
+/// Returns a broadcast [Stream] of events from the `streamIntsAgain` event channel.
+///
+/// Each call to this method creates a new [EventChannel], so it should
+/// not be called multiple times for the same `instanceName`. To deliver
+/// events to multiple listeners, call this method once and listen to the
+/// returned broadcast stream multiple times instead.
Stream<int> streamIntsAgain({String instanceName = ''}) {
if (instanceName.isNotEmpty) {
instanceName = '.$instanceName';
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 65afaca..bf3b956 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22
-version: 27.1.0 # This must match the version in lib/src/generator_tools.dart
+version: 27.1.1 # This must match the version in lib/src/generator_tools.dart
environment:
sdk: ^3.10.0
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 695b9a9..dce12d0 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -79,6 +79,46 @@
expect(code, contains(' two,'));
});
+ test('gen event channel api with usage docs on the generated method', () {
+ final api = AstEventChannelApi(
+ name: 'EventApi',
+ methods: <Method>[
+ Method(
+ name: 'streamEvents',
+ location: ApiLocation.host,
+ returnType: const TypeDeclaration(baseName: 'int', isNullable: false),
+ parameters: <Parameter>[],
+ documentationComments: <String>[' An example event stream.'],
+ ),
+ ],
+ );
+ final root = Root(apis: <Api>[api], classes: <Class>[], enums: <Enum>[]);
+ final sink = StringBuffer();
+ const generator = DartGenerator();
+ generator.generate(
+ const InternalDartOptions(ignoreLints: false),
+ root,
+ sink,
+ dartPackageName: DEFAULT_PACKAGE_NAME,
+ );
+ final code = sink.toString();
+ expect(code, contains('Stream<int> streamEvents('));
+ // The user's own documentation comments are emitted.
+ expect(code, contains('/// An example event stream.'));
+ // The generated usage documentation explains the channel-owning and
+ // broadcast semantics.
+ expect(
+ code,
+ contains('/// Returns a broadcast [Stream] of events from the `streamEvents` event channel.'),
+ );
+ expect(code, contains('not be called multiple times for the same `instanceName`'));
+ // The usage documentation is attached directly above the generated method.
+ expect(
+ code.indexOf('not be called multiple times'),
+ lessThan(code.indexOf('Stream<int> streamEvents(')),
+ );
+ });
+
test('gen one host api', () {
final root = Root(
apis: <Api>[