Skip to content

Commit cb18978

Browse files
authored
[pigeon] Adds annotation options to omit shared classes used in Event Channels (#8566)
fixes flutter/flutter#161291
1 parent bae29f6 commit cb18978

File tree

13 files changed

+200
-27
lines changed

13 files changed

+200
-27
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 24.1.0
2+
3+
* [kotlin, swift] Adds annotation options to omit shared classes used in Event Channels.
4+
15
## 24.0.0
26

37
* **Breaking Change** Relocates some files in `lib` that were not intended for direct client use to `lib/src`.

packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ private open class EventChannelMessagesPigeonCodec : StandardMessageCodec() {
7979

8080
val EventChannelMessagesPigeonMethodCodec = StandardMethodCodec(EventChannelMessagesPigeonCodec())
8181

82-
private class PigeonStreamHandler<T>(val wrapper: PigeonEventChannelWrapper<T>) :
83-
EventChannel.StreamHandler {
82+
private class EventChannelMessagesPigeonStreamHandler<T>(
83+
val wrapper: EventChannelMessagesPigeonEventChannelWrapper<T>
84+
) : EventChannel.StreamHandler {
8485
var pigeonSink: PigeonEventSink<T>? = null
8586

8687
override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
@@ -94,7 +95,7 @@ private class PigeonStreamHandler<T>(val wrapper: PigeonEventChannelWrapper<T>)
9495
}
9596
}
9697

97-
interface PigeonEventChannelWrapper<T> {
98+
interface EventChannelMessagesPigeonEventChannelWrapper<T> {
9899
open fun onListen(p0: Any?, sink: PigeonEventSink<T>) {}
99100

100101
open fun onCancel(p0: Any?) {}
@@ -114,7 +115,8 @@ class PigeonEventSink<T>(private val sink: EventChannel.EventSink) {
114115
}
115116
}
116117

117-
abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper<PlatformEvent> {
118+
abstract class StreamEventsStreamHandler :
119+
EventChannelMessagesPigeonEventChannelWrapper<PlatformEvent> {
118120
companion object {
119121
fun register(
120122
messenger: BinaryMessenger,
@@ -126,7 +128,8 @@ abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper<PlatformEve
126128
if (instanceName.isNotEmpty()) {
127129
channelName += ".$instanceName"
128130
}
129-
val internalStreamHandler = PigeonStreamHandler<PlatformEvent>(streamHandler)
131+
val internalStreamHandler =
132+
EventChannelMessagesPigeonStreamHandler<PlatformEvent>(streamHandler)
130133
EventChannel(messenger, channelName, EventChannelMessagesPigeonMethodCodec)
131134
.setStreamHandler(internalStreamHandler)
132135
}

packages/pigeon/lib/pigeon.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export 'src/dart/dart_generator.dart' show DartOptions;
99
export 'src/gobject/gobject_generator.dart' show GObjectOptions;
1010
export 'src/java/java_generator.dart' show JavaOptions;
1111
export 'src/kotlin/kotlin_generator.dart'
12-
show KotlinOptions, KotlinProxyApiOptions;
12+
show KotlinEventChannelOptions, KotlinOptions, KotlinProxyApiOptions;
1313
export 'src/objc/objc_generator.dart' show ObjcOptions;
1414
export 'src/pigeon_lib.dart';
15-
export 'src/swift/swift_generator.dart' show SwiftOptions, SwiftProxyApiOptions;
15+
export 'src/swift/swift_generator.dart'
16+
show SwiftEventChannelOptions, SwiftOptions, SwiftProxyApiOptions;

packages/pigeon/lib/src/ast.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import 'package:collection/collection.dart' show ListEquality;
66
import 'package:meta/meta.dart';
77

88
import 'generator_tools.dart';
9-
import 'kotlin/kotlin_generator.dart' show KotlinProxyApiOptions;
9+
import 'kotlin/kotlin_generator.dart'
10+
show KotlinEventChannelOptions, KotlinProxyApiOptions;
1011
import 'pigeon_lib.dart';
11-
import 'swift/swift_generator.dart' show SwiftProxyApiOptions;
12+
import 'swift/swift_generator.dart'
13+
show SwiftEventChannelOptions, SwiftProxyApiOptions;
1214

1315
typedef _ListEquals = bool Function(List<Object?>, List<Object?>);
1416

@@ -347,9 +349,17 @@ class AstEventChannelApi extends Api {
347349
AstEventChannelApi({
348350
required super.name,
349351
required super.methods,
352+
this.kotlinOptions,
353+
this.swiftOptions,
350354
super.documentationComments = const <String>[],
351355
});
352356

357+
/// Options for Kotlin generated code for Event Channels.
358+
final KotlinEventChannelOptions? kotlinOptions;
359+
360+
/// Options for Swift generated code for Event Channels.
361+
final SwiftEventChannelOptions? swiftOptions;
362+
353363
@override
354364
String toString() {
355365
return '(EventChannelApi name:$name methods:$methods documentationComments:$documentationComments)';

packages/pigeon/lib/src/generator_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'ast.dart';
1414
/// The current version of pigeon.
1515
///
1616
/// This must match the version in pubspec.yaml.
17-
const String pigeonVersion = '24.0.0';
17+
const String pigeonVersion = '24.1.0';
1818

1919
/// Read all the content from [stdin] to a String.
2020
String readStdin() {

packages/pigeon/lib/src/kotlin/kotlin_generator.dart

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ class KotlinProxyApiOptions {
115115
final int? minAndroidApi;
116116
}
117117

118+
/// Options for Kotlin implementation of Event Channels.
119+
class KotlinEventChannelOptions {
120+
/// Construct a [KotlinEventChannelOptions].
121+
const KotlinEventChannelOptions({this.includeSharedClasses = true});
122+
123+
/// Whether to include the shared Event Channel classes in generation.
124+
///
125+
/// This should only ever be set to false if you have another generated
126+
/// Kotlin file with Event Channels in the same directory.
127+
final bool includeSharedClasses;
128+
}
129+
118130
/// Class that manages all Kotlin code generation.
119131
class KotlinGenerator extends StructuredGenerator<KotlinOptions> {
120132
/// Instantiates a Kotlin Generator.
@@ -1018,8 +1030,8 @@ if (wrapped == null) {
10181030
}) {
10191031
indent.newln();
10201032
indent.format('''
1021-
private class PigeonStreamHandler<T>(
1022-
val wrapper: PigeonEventChannelWrapper<T>
1033+
private class ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<T>(
1034+
val wrapper: ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper<T>
10231035
) : EventChannel.StreamHandler {
10241036
var pigeonSink: PigeonEventSink<T>? = null
10251037
@@ -1034,12 +1046,15 @@ if (wrapped == null) {
10341046
}
10351047
}
10361048
1037-
interface PigeonEventChannelWrapper<T> {
1049+
interface ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper<T> {
10381050
open fun onListen(p0: Any?, sink: PigeonEventSink<T>) {}
10391051
10401052
open fun onCancel(p0: Any?) {}
10411053
}
1054+
''');
10421055

1056+
if (api.kotlinOptions?.includeSharedClasses ?? true) {
1057+
indent.format('''
10431058
class PigeonEventSink<T>(private val sink: EventChannel.EventSink) {
10441059
fun success(value: T) {
10451060
sink.success(value)
@@ -1054,18 +1069,19 @@ if (wrapped == null) {
10541069
}
10551070
}
10561071
''');
1072+
}
10571073
addDocumentationComments(
10581074
indent, api.documentationComments, _docCommentSpec);
10591075
for (final Method func in api.methods) {
10601076
indent.format('''
1061-
abstract class ${toUpperCamelCase(func.name)}StreamHandler : PigeonEventChannelWrapper<${_kotlinTypeForDartType(func.returnType)}> {
1077+
abstract class ${toUpperCamelCase(func.name)}StreamHandler : ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper<${_kotlinTypeForDartType(func.returnType)}> {
10621078
companion object {
10631079
fun register(messenger: BinaryMessenger, streamHandler: ${toUpperCamelCase(func.name)}StreamHandler, instanceName: String = "") {
10641080
var channelName: String = "${makeChannelName(api, func, dartPackageName)}"
10651081
if (instanceName.isNotEmpty()) {
10661082
channelName += ".\$instanceName"
10671083
}
1068-
val internalStreamHandler = PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
1084+
val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler)
10691085
EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodChannelCodec).setStreamHandler(internalStreamHandler)
10701086
}
10711087
}

packages/pigeon/lib/src/pigeon_lib.dart

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,13 @@ class ProxyApi {
169169
/// defined return type of the method definition.
170170
class EventChannelApi {
171171
/// Constructor.
172-
const EventChannelApi();
172+
const EventChannelApi({this.kotlinOptions, this.swiftOptions});
173+
174+
/// Options for Kotlin generated code for Event Channels.
175+
final KotlinEventChannelOptions? kotlinOptions;
176+
177+
/// Options for Swift generated code for Event Channels.
178+
final SwiftEventChannelOptions? swiftOptions;
173179
}
174180

175181
/// Metadata to annotation methods to control the selector used for objc output.
@@ -1082,6 +1088,8 @@ List<Error> _validateAst(Root root, String source) {
10821088
}
10831089
}
10841090

1091+
bool containsEventChannelApi = false;
1092+
10851093
for (final Api api in root.apis) {
10861094
final String? matchingPrefix = _findMatchingPrefixOrNull(
10871095
api.name,
@@ -1093,6 +1101,15 @@ List<Error> _validateAst(Root root, String source) {
10931101
'API name must not begin with "$matchingPrefix" in API "${api.name}"',
10941102
));
10951103
}
1104+
if (api is AstEventChannelApi) {
1105+
if (containsEventChannelApi) {
1106+
result.add(Error(
1107+
message:
1108+
'Event Channel methods must all be included in a single EventChannelApi',
1109+
));
1110+
}
1111+
containsEventChannelApi = true;
1112+
}
10961113
if (api is AstProxyApi) {
10971114
result.addAll(_validateProxyApi(
10981115
api,
@@ -1885,9 +1902,43 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor<Object?> {
18851902
_documentationCommentsParser(node.documentationComment?.tokens),
18861903
);
18871904
} else if (_hasMetadata(node.metadata, 'EventChannelApi')) {
1905+
final dart_ast.Annotation annotation = node.metadata.firstWhere(
1906+
(dart_ast.Annotation element) =>
1907+
element.name.name == 'EventChannelApi',
1908+
);
1909+
1910+
final Map<String, Object?> annotationMap = <String, Object?>{};
1911+
for (final dart_ast.Expression expression
1912+
in annotation.arguments!.arguments) {
1913+
if (expression is dart_ast.NamedExpression) {
1914+
annotationMap[expression.name.label.name] =
1915+
_expressionToMap(expression.expression);
1916+
}
1917+
}
1918+
1919+
SwiftEventChannelOptions? swiftOptions;
1920+
KotlinEventChannelOptions? kotlinOptions;
1921+
final Map<String, Object?>? swiftOptionsMap =
1922+
annotationMap['swiftOptions'] as Map<String, Object?>?;
1923+
if (swiftOptionsMap != null) {
1924+
swiftOptions = SwiftEventChannelOptions(
1925+
includeSharedClasses:
1926+
swiftOptionsMap['includeSharedClasses'] as bool? ?? true,
1927+
);
1928+
}
1929+
final Map<String, Object?>? kotlinOptionsMap =
1930+
annotationMap['kotlinOptions'] as Map<String, Object?>?;
1931+
if (kotlinOptionsMap != null) {
1932+
kotlinOptions = KotlinEventChannelOptions(
1933+
includeSharedClasses:
1934+
kotlinOptionsMap['includeSharedClasses'] as bool? ?? true,
1935+
);
1936+
}
18881937
_currentApi = AstEventChannelApi(
18891938
name: node.name.lexeme,
18901939
methods: <Method>[],
1940+
swiftOptions: swiftOptions,
1941+
kotlinOptions: kotlinOptions,
18911942
documentationComments:
18921943
_documentationCommentsParser(node.documentationComment?.tokens),
18931944
);

packages/pigeon/lib/src/swift/swift_generator.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ class SwiftProxyApiOptions {
125125
final bool supportsMacos;
126126
}
127127

128+
/// Options for Swift implementation of Event Channels.
129+
class SwiftEventChannelOptions {
130+
/// Constructs a [SwiftEventChannelOptions].
131+
const SwiftEventChannelOptions({this.includeSharedClasses = true});
132+
133+
/// Whether to include the error class in generation.
134+
///
135+
/// This should only ever be set to false if you have another generated
136+
/// Swift file with Event Channels in the same directory.
137+
final bool includeSharedClasses;
138+
}
139+
128140
/// Class that manages all Swift code generation.
129141
class SwiftGenerator extends StructuredGenerator<SwiftOptions> {
130142
/// Instantiates a Swift Generator.
@@ -1362,7 +1374,9 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
13621374
wrapper.onCancel(withArguments: arguments)
13631375
return nil
13641376
}
1365-
}
1377+
}''');
1378+
if (api.swiftOptions?.includeSharedClasses ?? true) {
1379+
indent.format('''
13661380
13671381
class PigeonEventChannelWrapper<ReturnType> {
13681382
func onListen(withArguments arguments: Any?, sink: PigeonEventSink<ReturnType>) {}
@@ -1390,6 +1404,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
13901404
13911405
}
13921406
''');
1407+
}
13931408
addDocumentationComments(
13941409
indent, api.documentationComments, _docCommentSpec);
13951410
for (final Method func in api.methods) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:pigeon/pigeon.dart';
6+
7+
// This file exists to test compilation for multi-file event channel usage.
8+
9+
@EventChannelApi(
10+
swiftOptions: SwiftEventChannelOptions(includeSharedClasses: false),
11+
kotlinOptions: KotlinEventChannelOptions(includeSharedClasses: false))
12+
abstract class EventChannelMethods {
13+
int streamIntsAgain();
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
//
5+
// Autogenerated from Pigeon, do not edit directly.
6+
// See also: https://pub.dev/packages/pigeon
7+
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
8+
9+
import 'dart:async';
10+
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
11+
12+
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
13+
import 'package:flutter/services.dart';
14+
15+
class _PigeonCodec extends StandardMessageCodec {
16+
const _PigeonCodec();
17+
@override
18+
void writeValue(WriteBuffer buffer, Object? value) {
19+
if (value is int) {
20+
buffer.putUint8(4);
21+
buffer.putInt64(value);
22+
} else {
23+
super.writeValue(buffer, value);
24+
}
25+
}
26+
27+
@override
28+
Object? readValueOfType(int type, ReadBuffer buffer) {
29+
switch (type) {
30+
default:
31+
return super.readValueOfType(type, buffer);
32+
}
33+
}
34+
}
35+
36+
const StandardMethodCodec pigeonMethodCodec =
37+
StandardMethodCodec(_PigeonCodec());
38+
39+
Stream<int> streamIntsAgain({String instanceName = ''}) {
40+
if (instanceName.isNotEmpty) {
41+
instanceName = '.$instanceName';
42+
}
43+
final EventChannel streamIntsAgainChannel = EventChannel(
44+
'dev.flutter.pigeon.pigeon_integration_tests.EventChannelMethods.streamIntsAgain$instanceName',
45+
pigeonMethodCodec);
46+
return streamIntsAgainChannel.receiveBroadcastStream().map((dynamic event) {
47+
return event as int;
48+
});
49+
}

0 commit comments

Comments
 (0)