diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 0aa95004cb7..0d8bf1004cd 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 24.1.0 + +* [kotlin, swift] Adds annotation options to omit shared classes used in Event Channels. + ## 24.0.0 * **Breaking Change** Relocates some files in `lib` that were not intended for direct client use to `lib/src`. diff --git a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt index ead5fe00f1b..0c0512df675 100644 --- a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt +++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt @@ -79,8 +79,9 @@ private open class EventChannelMessagesPigeonCodec : StandardMessageCodec() { val EventChannelMessagesPigeonMethodCodec = StandardMethodCodec(EventChannelMessagesPigeonCodec()) -private class PigeonStreamHandler(val wrapper: PigeonEventChannelWrapper) : - EventChannel.StreamHandler { +private class EventChannelMessagesPigeonStreamHandler( + val wrapper: EventChannelMessagesPigeonEventChannelWrapper +) : EventChannel.StreamHandler { var pigeonSink: PigeonEventSink? = null override fun onListen(p0: Any?, sink: EventChannel.EventSink) { @@ -94,7 +95,7 @@ private class PigeonStreamHandler(val wrapper: PigeonEventChannelWrapper) } } -interface PigeonEventChannelWrapper { +interface EventChannelMessagesPigeonEventChannelWrapper { open fun onListen(p0: Any?, sink: PigeonEventSink) {} open fun onCancel(p0: Any?) {} @@ -114,7 +115,8 @@ class PigeonEventSink(private val sink: EventChannel.EventSink) { } } -abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper { +abstract class StreamEventsStreamHandler : + EventChannelMessagesPigeonEventChannelWrapper { companion object { fun register( messenger: BinaryMessenger, @@ -126,7 +128,8 @@ abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper(streamHandler) + val internalStreamHandler = + EventChannelMessagesPigeonStreamHandler(streamHandler) EventChannel(messenger, channelName, EventChannelMessagesPigeonMethodCodec) .setStreamHandler(internalStreamHandler) } diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart index 8ea44e7e141..90d6b09ff5a 100644 --- a/packages/pigeon/lib/pigeon.dart +++ b/packages/pigeon/lib/pigeon.dart @@ -9,7 +9,8 @@ export 'src/dart/dart_generator.dart' show DartOptions; export 'src/gobject/gobject_generator.dart' show GObjectOptions; export 'src/java/java_generator.dart' show JavaOptions; export 'src/kotlin/kotlin_generator.dart' - show KotlinOptions, KotlinProxyApiOptions; + show KotlinEventChannelOptions, KotlinOptions, KotlinProxyApiOptions; export 'src/objc/objc_generator.dart' show ObjcOptions; export 'src/pigeon_lib.dart'; -export 'src/swift/swift_generator.dart' show SwiftOptions, SwiftProxyApiOptions; +export 'src/swift/swift_generator.dart' + show SwiftEventChannelOptions, SwiftOptions, SwiftProxyApiOptions; diff --git a/packages/pigeon/lib/src/ast.dart b/packages/pigeon/lib/src/ast.dart index 19d46523da4..116654458e5 100644 --- a/packages/pigeon/lib/src/ast.dart +++ b/packages/pigeon/lib/src/ast.dart @@ -6,9 +6,11 @@ import 'package:collection/collection.dart' show ListEquality; import 'package:meta/meta.dart'; import 'generator_tools.dart'; -import 'kotlin/kotlin_generator.dart' show KotlinProxyApiOptions; +import 'kotlin/kotlin_generator.dart' + show KotlinEventChannelOptions, KotlinProxyApiOptions; import 'pigeon_lib.dart'; -import 'swift/swift_generator.dart' show SwiftProxyApiOptions; +import 'swift/swift_generator.dart' + show SwiftEventChannelOptions, SwiftProxyApiOptions; typedef _ListEquals = bool Function(List, List); @@ -347,9 +349,17 @@ class AstEventChannelApi extends Api { AstEventChannelApi({ required super.name, required super.methods, + this.kotlinOptions, + this.swiftOptions, super.documentationComments = const [], }); + /// Options for Kotlin generated code for Event Channels. + final KotlinEventChannelOptions? kotlinOptions; + + /// Options for Swift generated code for Event Channels. + final SwiftEventChannelOptions? swiftOptions; + @override String toString() { return '(EventChannelApi name:$name methods:$methods documentationComments:$documentationComments)'; diff --git a/packages/pigeon/lib/src/generator_tools.dart b/packages/pigeon/lib/src/generator_tools.dart index 2c89f595d9a..1398b446035 100644 --- a/packages/pigeon/lib/src/generator_tools.dart +++ b/packages/pigeon/lib/src/generator_tools.dart @@ -14,7 +14,7 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '24.0.0'; +const String pigeonVersion = '24.1.0'; /// Read all the content from [stdin] to a String. String readStdin() { diff --git a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart index 9f33dc58b36..6c51d8a0b0d 100644 --- a/packages/pigeon/lib/src/kotlin/kotlin_generator.dart +++ b/packages/pigeon/lib/src/kotlin/kotlin_generator.dart @@ -115,6 +115,18 @@ class KotlinProxyApiOptions { final int? minAndroidApi; } +/// Options for Kotlin implementation of Event Channels. +class KotlinEventChannelOptions { + /// Construct a [KotlinEventChannelOptions]. + const KotlinEventChannelOptions({this.includeSharedClasses = true}); + + /// Whether to include the shared Event Channel classes in generation. + /// + /// This should only ever be set to false if you have another generated + /// Kotlin file with Event Channels in the same directory. + final bool includeSharedClasses; +} + /// Class that manages all Kotlin code generation. class KotlinGenerator extends StructuredGenerator { /// Instantiates a Kotlin Generator. @@ -1018,8 +1030,8 @@ if (wrapped == null) { }) { indent.newln(); indent.format(''' - private class PigeonStreamHandler( - val wrapper: PigeonEventChannelWrapper + private class ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler( + val wrapper: ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper ) : EventChannel.StreamHandler { var pigeonSink: PigeonEventSink? = null @@ -1034,12 +1046,15 @@ if (wrapped == null) { } } - interface PigeonEventChannelWrapper { + interface ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper { open fun onListen(p0: Any?, sink: PigeonEventSink) {} open fun onCancel(p0: Any?) {} } + '''); + if (api.kotlinOptions?.includeSharedClasses ?? true) { + indent.format(''' class PigeonEventSink(private val sink: EventChannel.EventSink) { fun success(value: T) { sink.success(value) @@ -1054,18 +1069,19 @@ if (wrapped == null) { } } '''); + } addDocumentationComments( indent, api.documentationComments, _docCommentSpec); for (final Method func in api.methods) { indent.format(''' - abstract class ${toUpperCamelCase(func.name)}StreamHandler : PigeonEventChannelWrapper<${_kotlinTypeForDartType(func.returnType)}> { + abstract class ${toUpperCamelCase(func.name)}StreamHandler : ${generatorOptions.fileSpecificClassNameComponent}PigeonEventChannelWrapper<${_kotlinTypeForDartType(func.returnType)}> { 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" } - val internalStreamHandler = PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler) + val internalStreamHandler = ${generatorOptions.fileSpecificClassNameComponent}PigeonStreamHandler<${_kotlinTypeForDartType(func.returnType)}>(streamHandler) EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodChannelCodec).setStreamHandler(internalStreamHandler) } } diff --git a/packages/pigeon/lib/src/pigeon_lib.dart b/packages/pigeon/lib/src/pigeon_lib.dart index d177c1ee7e8..b7238c1f250 100644 --- a/packages/pigeon/lib/src/pigeon_lib.dart +++ b/packages/pigeon/lib/src/pigeon_lib.dart @@ -169,7 +169,13 @@ class ProxyApi { /// defined return type of the method definition. class EventChannelApi { /// Constructor. - const EventChannelApi(); + const EventChannelApi({this.kotlinOptions, this.swiftOptions}); + + /// Options for Kotlin generated code for Event Channels. + final KotlinEventChannelOptions? kotlinOptions; + + /// Options for Swift generated code for Event Channels. + final SwiftEventChannelOptions? swiftOptions; } /// Metadata to annotation methods to control the selector used for objc output. @@ -1082,6 +1088,8 @@ List _validateAst(Root root, String source) { } } + bool containsEventChannelApi = false; + for (final Api api in root.apis) { final String? matchingPrefix = _findMatchingPrefixOrNull( api.name, @@ -1093,6 +1101,15 @@ List _validateAst(Root root, String source) { 'API name must not begin with "$matchingPrefix" in API "${api.name}"', )); } + if (api is AstEventChannelApi) { + if (containsEventChannelApi) { + result.add(Error( + message: + 'Event Channel methods must all be included in a single EventChannelApi', + )); + } + containsEventChannelApi = true; + } if (api is AstProxyApi) { result.addAll(_validateProxyApi( api, @@ -1885,9 +1902,43 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { _documentationCommentsParser(node.documentationComment?.tokens), ); } else if (_hasMetadata(node.metadata, 'EventChannelApi')) { + final dart_ast.Annotation annotation = node.metadata.firstWhere( + (dart_ast.Annotation element) => + element.name.name == 'EventChannelApi', + ); + + final Map annotationMap = {}; + for (final dart_ast.Expression expression + in annotation.arguments!.arguments) { + if (expression is dart_ast.NamedExpression) { + annotationMap[expression.name.label.name] = + _expressionToMap(expression.expression); + } + } + + SwiftEventChannelOptions? swiftOptions; + KotlinEventChannelOptions? kotlinOptions; + final Map? swiftOptionsMap = + annotationMap['swiftOptions'] as Map?; + if (swiftOptionsMap != null) { + swiftOptions = SwiftEventChannelOptions( + includeSharedClasses: + swiftOptionsMap['includeSharedClasses'] as bool? ?? true, + ); + } + final Map? kotlinOptionsMap = + annotationMap['kotlinOptions'] as Map?; + if (kotlinOptionsMap != null) { + kotlinOptions = KotlinEventChannelOptions( + includeSharedClasses: + kotlinOptionsMap['includeSharedClasses'] as bool? ?? true, + ); + } _currentApi = AstEventChannelApi( name: node.name.lexeme, methods: [], + swiftOptions: swiftOptions, + kotlinOptions: kotlinOptions, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); diff --git a/packages/pigeon/lib/src/swift/swift_generator.dart b/packages/pigeon/lib/src/swift/swift_generator.dart index 23be57b068e..9ff3655eb01 100644 --- a/packages/pigeon/lib/src/swift/swift_generator.dart +++ b/packages/pigeon/lib/src/swift/swift_generator.dart @@ -125,6 +125,18 @@ class SwiftProxyApiOptions { final bool supportsMacos; } +/// Options for Swift implementation of Event Channels. +class SwiftEventChannelOptions { + /// Constructs a [SwiftEventChannelOptions]. + const SwiftEventChannelOptions({this.includeSharedClasses = true}); + + /// Whether to include the error class in generation. + /// + /// This should only ever be set to false if you have another generated + /// Swift file with Event Channels in the same directory. + final bool includeSharedClasses; +} + /// Class that manages all Swift code generation. class SwiftGenerator extends StructuredGenerator { /// Instantiates a Swift Generator. @@ -1362,7 +1374,9 @@ private func nilOrValue(_ value: Any?) -> T? { wrapper.onCancel(withArguments: arguments) return nil } - } + }'''); + if (api.swiftOptions?.includeSharedClasses ?? true) { + indent.format(''' class PigeonEventChannelWrapper { func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {} @@ -1390,6 +1404,7 @@ private func nilOrValue(_ value: Any?) -> T? { } '''); + } addDocumentationComments( indent, api.documentationComments, _docCommentSpec); for (final Method func in api.methods) { diff --git a/packages/pigeon/pigeons/event_channel_without_classes_tests.dart b/packages/pigeon/pigeons/event_channel_without_classes_tests.dart new file mode 100644 index 00000000000..a568f0ae4fa --- /dev/null +++ b/packages/pigeon/pigeons/event_channel_without_classes_tests.dart @@ -0,0 +1,14 @@ +// Copyright 2013 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. + +import 'package:pigeon/pigeon.dart'; + +// This file exists to test compilation for multi-file event channel usage. + +@EventChannelApi( + swiftOptions: SwiftEventChannelOptions(includeSharedClasses: false), + kotlinOptions: KotlinEventChannelOptions(includeSharedClasses: false)) +abstract class EventChannelMethods { + int streamIntsAgain(); +} 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 new file mode 100644 index 00000000000..4c6b0307cf5 --- /dev/null +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_without_classes_tests.gen.dart @@ -0,0 +1,49 @@ +// Copyright 2013 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. +// +// Autogenerated from Pigeon, do not edit directly. +// See also: https://pub.dev/packages/pigeon +// 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 + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +const StandardMethodCodec pigeonMethodCodec = + StandardMethodCodec(_PigeonCodec()); + +Stream streamIntsAgain({String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.$instanceName'; + } + final EventChannel streamIntsAgainChannel = EventChannel( + 'dev.flutter.pigeon.pigeon_integration_tests.EventChannelMethods.streamIntsAgain$instanceName', + pigeonMethodCodec); + return streamIntsAgainChannel.receiveBroadcastStream().map((dynamic event) { + return event as int; + }); +} diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt index 7b36b0a4350..1f24c42aed4 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt @@ -398,8 +398,9 @@ private open class EventChannelTestsPigeonCodec : StandardMessageCodec() { val EventChannelTestsPigeonMethodCodec = StandardMethodCodec(EventChannelTestsPigeonCodec()) -private class PigeonStreamHandler(val wrapper: PigeonEventChannelWrapper) : - EventChannel.StreamHandler { +private class EventChannelTestsPigeonStreamHandler( + val wrapper: EventChannelTestsPigeonEventChannelWrapper +) : EventChannel.StreamHandler { var pigeonSink: PigeonEventSink? = null override fun onListen(p0: Any?, sink: EventChannel.EventSink) { @@ -413,7 +414,7 @@ private class PigeonStreamHandler(val wrapper: PigeonEventChannelWrapper) } } -interface PigeonEventChannelWrapper { +interface EventChannelTestsPigeonEventChannelWrapper { open fun onListen(p0: Any?, sink: PigeonEventSink) {} open fun onCancel(p0: Any?) {} @@ -433,7 +434,7 @@ class PigeonEventSink(private val sink: EventChannel.EventSink) { } } -abstract class StreamIntsStreamHandler : PigeonEventChannelWrapper { +abstract class StreamIntsStreamHandler : EventChannelTestsPigeonEventChannelWrapper { companion object { fun register( messenger: BinaryMessenger, @@ -445,14 +446,15 @@ abstract class StreamIntsStreamHandler : PigeonEventChannelWrapper { if (instanceName.isNotEmpty()) { channelName += ".$instanceName" } - val internalStreamHandler = PigeonStreamHandler(streamHandler) + val internalStreamHandler = EventChannelTestsPigeonStreamHandler(streamHandler) EventChannel(messenger, channelName, EventChannelTestsPigeonMethodCodec) .setStreamHandler(internalStreamHandler) } } } -abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper { +abstract class StreamEventsStreamHandler : + EventChannelTestsPigeonEventChannelWrapper { companion object { fun register( messenger: BinaryMessenger, @@ -464,14 +466,15 @@ abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper(streamHandler) + val internalStreamHandler = EventChannelTestsPigeonStreamHandler(streamHandler) EventChannel(messenger, channelName, EventChannelTestsPigeonMethodCodec) .setStreamHandler(internalStreamHandler) } } } -abstract class StreamConsistentNumbersStreamHandler : PigeonEventChannelWrapper { +abstract class StreamConsistentNumbersStreamHandler : + EventChannelTestsPigeonEventChannelWrapper { companion object { fun register( messenger: BinaryMessenger, @@ -483,7 +486,7 @@ abstract class StreamConsistentNumbersStreamHandler : PigeonEventChannelWrapper< if (instanceName.isNotEmpty()) { channelName += ".$instanceName" } - val internalStreamHandler = PigeonStreamHandler(streamHandler) + val internalStreamHandler = EventChannelTestsPigeonStreamHandler(streamHandler) EventChannel(messenger, channelName, EventChannelTestsPigeonMethodCodec) .setStreamHandler(internalStreamHandler) } diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 94a423d1ca7..ee82d5219c8 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon 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: 24.0.0 # This must match the version in lib/src/generator_tools.dart +version: 24.1.0 # This must match the version in lib/src/generator_tools.dart environment: sdk: ^3.4.0 diff --git a/packages/pigeon/tool/shared/generation.dart b/packages/pigeon/tool/shared/generation.dart index 057b8b964dd..12b85a4973e 100644 --- a/packages/pigeon/tool/shared/generation.dart +++ b/packages/pigeon/tool/shared/generation.dart @@ -30,6 +30,12 @@ const Map> _unsupportedFiles = GeneratorLanguage.java, GeneratorLanguage.objc, }, + 'event_channel_without_classes_tests': { + GeneratorLanguage.cpp, + GeneratorLanguage.gobject, + GeneratorLanguage.java, + GeneratorLanguage.objc, + }, 'proxy_api_tests': { GeneratorLanguage.cpp, GeneratorLanguage.gobject, @@ -84,6 +90,7 @@ Future generateTestPigeons( 'core_tests', 'enum', 'event_channel_tests', + 'event_channel_without_classes_tests', 'flutter_unittests', // Only for Dart unit tests in shared_test_plugin_code 'message', 'multiple_arity',