diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 4b1c34f08252..8bfef8538741 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,8 @@ +## 22.7.0 + +* [swift, kotlin] Adds event channel support. +* [swift, kotlin] Adds `sealed` class inheritance support. + ## 22.6.2 * Removes the `@protected` annotation from the InstanceManager field of the diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index cc375f5e2faa..39df85d65f41 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -26,6 +26,8 @@ Pigeon uses the `StandardMessageCodec` so it supports Custom classes, nested datatypes, and enums are also supported. +Basic inheritance with empty `sealed` parent classes is allowed only in the Swift, Kotlin, and Dart generators. + Nullable enums in Objective-C generated code will be wrapped in a class to allow for nullability. By default, custom classes in Swift are defined as structs. @@ -104,9 +106,10 @@ to the api to allow for multiple instances to be created and operate in parallel 1) Method declarations on the API classes should have arguments and a return value whose types are defined in the file, are supported datatypes, or are `void`. -1) Generics are supported, but can currently only be used with nullable types - (example: `List`). -1) Objc and Swift have special naming conventions that can be utilized with the +1) Event channels are supported only on the Swift, Kotlin, and Dart generators. +1) Event channel methods should be wrapped in an `abstract class` with the metadata `@EventChannelApi`. +1) Event channel definitions should not include the `Stream` return type, just the type that is being streamed. +1) Objective-C and Swift have special naming conventions that can be utilized with the `@ObjCSelector` and `@SwiftFunction` respectively. ### Flutter calling into iOS steps diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md index 7e7fbb6d5af3..e7a0db2cc635 100644 --- a/packages/pigeon/example/README.md +++ b/packages/pigeon/example/README.md @@ -360,6 +360,118 @@ pigeon_example_package_message_flutter_api_flutter_method( self->flutter_api, "hello", nullptr, flutter_method_cb, self); ``` +## Event Channel Example + +This example gives a basic overview of how to use Pigeon to set up an event channel. + +### Dart input + + +```dart +@EventChannelApi() +abstract class PlatformEvent { + SealedBaseClass streamEvents(); +} +``` + +### Dart + +The generated Dart code will include a method that returns a `Stream` when invoked. + + +```dart +Stream getEventStream() async* { + final Stream events = streamEvents(); + await for (final SealedBaseClass event in events) { + switch (event) { + case IntEvent(): + final int intData = event.data; + yield '$intData, '; + case StringEvent(): + final String stringData = event.data; + yield '$stringData, '; + } + } +} +``` + +### Swift + +Define the stream handler class that will handle the events. + + +```swift +class EventListener: StreamEventsStreamHandler { + var eventSink: PigeonEventSink? + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + eventSink = sink + } + + func onIntEvent(event: Int64) { + if let eventSink = eventSink { + eventSink.success(IntEvent(data: event)) + } + } + + func onStringEvent(event: String) { + if let eventSink = eventSink { + eventSink.success(StringEvent(data: event)) + } + } + + func onEventsDone() { + eventSink?.endOfStream() + eventSink = nil + } +} +``` + +Register the handler with the generated method. + + +```swift +let eventListener = EventListener() +StreamEventsStreamHandler.register(with: controller.binaryMessenger, wrapper: eventListener) +``` + +### Kotlin + +Define the stream handler class that will handle the events. + + +```kotlin +class EventListener : StreamEventsStreamHandler() { + private var eventSink: PigeonEventSink? = null + + override fun onListen(p0: Any?, sink: PigeonEventSink) { + eventSink = sink + } + + fun onIntEvent(event: Long) { + eventSink?.success(IntEvent(data = event)) + } + + fun onStringEvent(event: String) { + eventSink?.success(StringEvent(data = event)) + } + + fun onEventsDone() { + eventSink?.endOfStream() + eventSink = null + } +} +``` + + +Register the handler with the generated method. + + +```kotlin +val eventListener = EventListener() +StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener) +``` + ## Swift / Kotlin Plugin Example A downloadable example of using Pigeon to create a Flutter Plugin with Swift and diff --git a/packages/pigeon/example/app/.gitignore b/packages/pigeon/example/app/.gitignore index 24476c5d1eb5..6c319542b342 100644 --- a/packages/pigeon/example/app/.gitignore +++ b/packages/pigeon/example/app/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related 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 new file mode 100644 index 000000000000..5c8300f620ce --- /dev/null +++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt @@ -0,0 +1,134 @@ +// 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 +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +/** + * Generated class from Pigeon that represents data sent in messages. This class should not be + * extended by any user class outside of the generated file. + */ +sealed class SealedBaseClass +/** Generated class from Pigeon that represents data sent in messages. */ +data class IntEvent(val data: Long) : SealedBaseClass() { + companion object { + fun fromList(pigeonVar_list: List): IntEvent { + val data = pigeonVar_list[0] as Long + return IntEvent(data) + } + } + + fun toList(): List { + return listOf( + data, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class StringEvent(val data: String) : SealedBaseClass() { + companion object { + fun fromList(pigeonVar_list: List): StringEvent { + val data = pigeonVar_list[0] as String + return StringEvent(data) + } + } + + fun toList(): List { + return listOf( + data, + ) + } +} + +private open class EventChannelMessagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { IntEvent.fromList(it) } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { StringEvent.fromList(it) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is IntEvent -> { + stream.write(129) + writeValue(stream, value.toList()) + } + is StringEvent -> { + stream.write(130) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +val EventChannelMessagesPigeonMethodCodec = StandardMethodCodec(EventChannelMessagesPigeonCodec()) + +private class PigeonStreamHandler(val wrapper: PigeonEventChannelWrapper) : + EventChannel.StreamHandler { + var pigeonSink: PigeonEventSink? = null + + override fun onListen(p0: Any?, sink: EventChannel.EventSink) { + pigeonSink = PigeonEventSink(sink) + wrapper.onListen(p0, pigeonSink!!) + } + + override fun onCancel(p0: Any?) { + pigeonSink = null + wrapper.onCancel(p0) + } +} + +interface PigeonEventChannelWrapper { + open fun onListen(p0: Any?, sink: PigeonEventSink) {} + + open fun onCancel(p0: Any?) {} +} + +class PigeonEventSink(private val sink: EventChannel.EventSink) { + fun success(value: T) { + sink.success(value) + } + + fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + sink.error(errorCode, errorMessage, errorDetails) + } + + fun endOfStream() { + sink.endOfStream() + } +} + +abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper { + companion object { + fun register( + messenger: BinaryMessenger, + streamHandler: StreamEventsStreamHandler, + instanceName: String = "" + ) { + var channelName: String = + "dev.flutter.pigeon.pigeon_example_package.PlatformEvent.streamEvents" + if (instanceName.isNotEmpty()) { + channelName += ".$instanceName" + } + val internalStreamHandler = PigeonStreamHandler(streamHandler) + EventChannel(messenger, channelName, EventChannelMessagesPigeonMethodCodec) + .setStreamHandler(internalStreamHandler) + } + } +} diff --git a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt index af0d181a24ab..e03692c4557a 100644 --- a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt +++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt @@ -6,8 +6,15 @@ package dev.flutter.pigeon_example_app import ExampleHostApi import FlutterError +import IntEvent import MessageData import MessageFlutterApi +import PigeonEventSink +import SealedBaseClass +import StreamEventsStreamHandler +import StringEvent +import android.os.Handler +import android.os.Looper import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.plugins.FlutterPlugin @@ -49,11 +56,66 @@ private class PigeonFlutterApi(binding: FlutterPlugin.FlutterPluginBinding) { } // #enddocregion kotlin-class-flutter +// #docregion kotlin-class-event +class EventListener : StreamEventsStreamHandler() { + private var eventSink: PigeonEventSink? = null + + override fun onListen(p0: Any?, sink: PigeonEventSink) { + eventSink = sink + } + + fun onIntEvent(event: Long) { + eventSink?.success(IntEvent(data = event)) + } + + fun onStringEvent(event: String) { + eventSink?.success(StringEvent(data = event)) + } + + fun onEventsDone() { + eventSink?.endOfStream() + eventSink = null + } +} +// #enddocregion kotlin-class-event + +fun sendEvents(eventListener: EventListener) { + val handler = Handler(Looper.getMainLooper()) + var count: Int = 0 + val r: Runnable = + object : Runnable { + override fun run() { + if (count >= 100) { + handler.post { eventListener.onEventsDone() } + } else { + if (count % 2 == 0) { + handler.post { + eventListener.onIntEvent(count.toLong()) + count++ + } + } else { + handler.post { + eventListener.onStringEvent(count.toString()) + count++ + } + } + handler.postDelayed(this, 1000) + } + } + } + handler.post(r) +} + class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) val api = PigeonApiImplementation() ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api) + // #docregion kotlin-init-event + val eventListener = EventListener() + StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener) + // #enddocregion kotlin-init-event + sendEvents(eventListener) } } diff --git a/packages/pigeon/example/app/ios/Flutter/AppFrameworkInfo.plist b/packages/pigeon/example/app/ios/Flutter/AppFrameworkInfo.plist index 9625e105df39..7c5696400627 100644 --- a/packages/pigeon/example/app/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/pigeon/example/app/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/packages/pigeon/example/app/ios/Podfile b/packages/pigeon/example/app/ios/Podfile index 5fbdfa333224..01d4aa611bb9 100644 --- a/packages/pigeon/example/app/ios/Podfile +++ b/packages/pigeon/example/app/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj b/packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj index 6541f88ab296..f3a76aee17e9 100644 --- a/packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,12 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3368472729F02D040090029A /* Messages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3368472629F02D040090029A /* Messages.g.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 477F5F842CCC1D8D006725C4 /* EventChannelMessages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 477F5F832CCC1D8D006725C4 /* EventChannelMessages.g.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F7E907CAAC4B02307953628E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EABF7C39D02C68F9E6B4AA70 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -32,8 +34,10 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2F6990FD9E99EB154922F8AD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3368472629F02D040090029A /* Messages.g.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Messages.g.swift; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 477F5F832CCC1D8D006725C4 /* EventChannelMessages.g.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventChannelMessages.g.swift; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -44,6 +48,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB1EB56E8909F53184417179 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C25B8BD91AF396D9449B25F5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + EABF7C39D02C68F9E6B4AA70 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -51,12 +58,32 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F7E907CAAC4B02307953628E /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4F8299774F028C407687C96B /* Frameworks */ = { + isa = PBXGroup; + children = ( + EABF7C39D02C68F9E6B4AA70 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5170560B875A27998DBDBE7B /* Pods */ = { + isa = PBXGroup; + children = ( + 2F6990FD9E99EB154922F8AD /* Pods-Runner.debug.xcconfig */, + AB1EB56E8909F53184417179 /* Pods-Runner.release.xcconfig */, + C25B8BD91AF396D9449B25F5 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -74,6 +101,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 5170560B875A27998DBDBE7B /* Pods */, + 4F8299774F028C407687C96B /* Frameworks */, ); sourceTree = ""; }; @@ -88,6 +117,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 477F5F832CCC1D8D006725C4 /* EventChannelMessages.g.swift */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -108,12 +138,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 93861A2E607F9CF8AD253B1D /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 8678AE2DB006310D29292EB1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -130,7 +162,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -188,6 +220,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 8678AE2DB006310D29292EB1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 93861A2E607F9CF8AD253B1D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -213,6 +284,7 @@ 3368472729F02D040090029A /* Messages.g.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 477F5F842CCC1D8D006725C4 /* EventChannelMessages.g.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -279,7 +351,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -356,7 +428,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -405,7 +477,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/pigeon/example/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pigeon/example/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e42adcb34c2d..8e3ca5dfe193 100644 --- a/packages/pigeon/example/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pigeon/example/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift index 64a73d05ca28..4be4828a323d 100644 --- a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift +++ b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift @@ -46,6 +46,53 @@ private class PigeonFlutterApi { } // #enddocregion swift-class-flutter +// #docregion swift-class-event +class EventListener: StreamEventsStreamHandler { + var eventSink: PigeonEventSink? + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + eventSink = sink + } + + func onIntEvent(event: Int64) { + if let eventSink = eventSink { + eventSink.success(IntEvent(data: event)) + } + } + + func onStringEvent(event: String) { + if let eventSink = eventSink { + eventSink.success(StringEvent(data: event)) + } + } + + func onEventsDone() { + eventSink?.endOfStream() + eventSink = nil + } +} +// #enddocregion swift-class-event + +func sendEvents(_ eventListener: EventListener) { + var timer: Timer? + var count: Int64 = 0 + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in + DispatchQueue.main.async { + if count >= 100 { + eventListener.onEventsDone() + timer?.invalidate() + } else { + if (count % 2) == 0 { + eventListener.onIntEvent(event: Int64(count)) + } else { + eventListener.onStringEvent(event: String(count)) + } + count += 1 + } + } + } +} + @main @objc class AppDelegate: FlutterAppDelegate { override func application( @@ -57,6 +104,11 @@ private class PigeonFlutterApi { let controller = window?.rootViewController as! FlutterViewController let api = PigeonApiImplementation() ExampleHostApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: api) + // #docregion swift-init-event + let eventListener = EventListener() + StreamEventsStreamHandler.register(with: controller.binaryMessenger, wrapper: eventListener) + // #enddocregion swift-init-event + sendEvents(eventListener) return super.application(application, didFinishLaunchingWithOptions: launchOptions) diff --git a/packages/pigeon/example/app/ios/Runner/EventChannelMessages.g.swift b/packages/pigeon/example/app/ios/Runner/EventChannelMessages.g.swift new file mode 100644 index 000000000000..db6355e8a6f6 --- /dev/null +++ b/packages/pigeon/example/app/ios/Runner/EventChannelMessages.g.swift @@ -0,0 +1,179 @@ +// 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 + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +/// Generated class from Pigeon that represents data sent in messages. +/// This class should not be extended by any user class outside of the generated file. +protocol SealedBaseClass { + +} + +/// Generated class from Pigeon that represents data sent in messages. +struct IntEvent: SealedBaseClass { + var data: Int64 + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> IntEvent? { + let data = pigeonVar_list[0] as! Int64 + + return IntEvent( + data: data + ) + } + func toList() -> [Any?] { + return [ + data + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct StringEvent: SealedBaseClass { + var data: String + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> StringEvent? { + let data = pigeonVar_list[0] as! String + + return StringEvent( + data: data + ) + } + func toList() -> [Any?] { + return [ + data + ] + } +} + +private class EventChannelMessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return IntEvent.fromList(self.readValue() as! [Any?]) + case 130: + return StringEvent.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class EventChannelMessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? IntEvent { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? StringEvent { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class EventChannelMessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return EventChannelMessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return EventChannelMessagesPigeonCodecWriter(data: data) + } +} + +class EventChannelMessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = EventChannelMessagesPigeonCodec( + readerWriter: EventChannelMessagesPigeonCodecReaderWriter()) +} + +var eventChannelMessagesPigeonMethodCodec = FlutterStandardMethodCodec( + readerWriter: EventChannelMessagesPigeonCodecReaderWriter()) + +private class PigeonStreamHandler: NSObject, FlutterStreamHandler { + private let wrapper: PigeonEventChannelWrapper + private var pigeonSink: PigeonEventSink? = nil + + init(wrapper: PigeonEventChannelWrapper) { + self.wrapper = wrapper + } + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + pigeonSink = PigeonEventSink(events) + wrapper.onListen(withArguments: arguments, sink: pigeonSink!) + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + pigeonSink = nil + wrapper.onCancel(withArguments: arguments) + return nil + } +} + +class PigeonEventChannelWrapper { + func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {} + func onCancel(withArguments arguments: Any?) {} +} + +class PigeonEventSink { + private let sink: FlutterEventSink + + init(_ sink: @escaping FlutterEventSink) { + self.sink = sink + } + + func success(_ value: ReturnType) { + sink(value) + } + + func error(code: String, message: String?, details: Any?) { + sink(FlutterError(code: code, message: message, details: details)) + } + + func endOfStream() { + sink(FlutterEndOfEventStream) + } + +} + +class StreamEventsStreamHandler: PigeonEventChannelWrapper { + static func register( + with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamEventsStreamHandler + ) { + var channelName = "dev.flutter.pigeon.pigeon_example_package.PlatformEvent.streamEvents" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(streamHandler: wrapper) + let channel = FlutterEventChannel( + name: channelName, binaryMessenger: messenger, codec: eventChannelMessagesPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} diff --git a/packages/pigeon/example/app/lib/main.dart b/packages/pigeon/example/app/lib/main.dart index cc158abe0c1f..f7048f7b98e4 100644 --- a/packages/pigeon/example/app/lib/main.dart +++ b/packages/pigeon/example/app/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'src/event_channel_messages.g.dart'; import 'src/messages.g.dart'; // #docregion main-dart-flutter @@ -85,6 +86,22 @@ class _MyHomePageState extends State { } // #enddocregion main-dart + // #docregion main-dart-event + Stream getEventStream() async* { + final Stream events = streamEvents(); + await for (final SealedBaseClass event in events) { + switch (event) { + case IntEvent(): + final int intData = event.data; + yield '$intData, '; + case StringEvent(): + final String stringData = event.data; + yield '$stringData, '; + } + } + } + // #enddocregion main-dart-event + @override void initState() { super.initState(); @@ -101,6 +118,7 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { + String allData = ''; return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, @@ -114,6 +132,17 @@ class _MyHomePageState extends State { _hostCallResult ?? 'Waiting for host language...', ), if (_hostCallResult == null) const CircularProgressIndicator(), + StreamBuilder( + stream: getEventStream(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + allData += snapshot.data ?? ''; + return Text(allData); + } else { + return const CircularProgressIndicator(); + } + }, + ) ], ), ), 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 new file mode 100644 index 000000000000..be2b0bc12eb7 --- /dev/null +++ b/packages/pigeon/example/app/lib/src/event_channel_messages.g.dart @@ -0,0 +1,102 @@ +// 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'; + +sealed class SealedBaseClass {} + +class IntEvent extends SealedBaseClass { + IntEvent({ + required this.data, + }); + + int data; + + Object encode() { + return [ + data, + ]; + } + + static IntEvent decode(Object result) { + result as List; + return IntEvent( + data: result[0]! as int, + ); + } +} + +class StringEvent extends SealedBaseClass { + StringEvent({ + required this.data, + }); + + String data; + + Object encode() { + return [ + data, + ]; + } + + static StringEvent decode(Object result) { + result as List; + return StringEvent( + data: result[0]! as String, + ); + } +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is IntEvent) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is StringEvent) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return IntEvent.decode(readValue(buffer)!); + case 130: + return StringEvent.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +const StandardMethodCodec pigeonMethodCodec = + StandardMethodCodec(_PigeonCodec()); + +Stream streamEvents({String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.$instanceName'; + } + const EventChannel streamEventsChannel = EventChannel( + 'dev.flutter.pigeon.pigeon_example_package.PlatformEvent.streamEvents', + pigeonMethodCodec); + return streamEventsChannel.receiveBroadcastStream().map((dynamic event) { + return event as SealedBaseClass; + }); +} diff --git a/packages/pigeon/example/app/pigeons/event_channel_messages.dart b/packages/pigeon/example/app/pigeons/event_channel_messages.dart new file mode 100644 index 000000000000..84dc93c99f78 --- /dev/null +++ b/packages/pigeon/example/app/pigeons/event_channel_messages.dart @@ -0,0 +1,43 @@ +// 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'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/event_channel_messages.g.dart', + dartOptions: DartOptions(), + cppOptions: CppOptions(namespace: 'pigeon_example'), + kotlinOut: + 'android/app/src/main/kotlin/dev/flutter/pigeon_example_app/EventChannelMessages.g.kt', + kotlinOptions: KotlinOptions( + includeErrorClass: false, + ), + swiftOut: 'ios/Runner/EventChannelMessages.g.swift', + swiftOptions: SwiftOptions( + includeErrorClass: false, + ), + copyrightHeader: 'pigeons/copyright.txt', + dartPackageName: 'pigeon_example_package', +)) + +// #docregion sealed-definitions +sealed class SealedBaseClass {} + +class IntEvent extends SealedBaseClass { + IntEvent(this.data); + int data; +} + +class StringEvent extends SealedBaseClass { + StringEvent(this.data); + String data; +} +// #enddocregion sealed-definitions + +// #docregion event-definitions +@EventChannelApi() +abstract class PlatformEvent { + SealedBaseClass streamEvents(); +} +// #enddocregion event-definitions diff --git a/packages/pigeon/example/app/pigeons/messages.dart b/packages/pigeon/example/app/pigeons/messages.dart index 2112fed96371..421e6ce1c9f8 100644 --- a/packages/pigeon/example/app/pigeons/messages.dart +++ b/packages/pigeon/example/app/pigeons/messages.dart @@ -30,8 +30,6 @@ import 'package:pigeon/pigeon.dart'; )) // #enddocregion config -// This file and ./messages_test.dart must be identical below this line. - // #docregion host-definitions enum Code { one, two } diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 1c93d4630e30..5e65f31c98ac 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -341,6 +341,21 @@ class AstProxyApi extends Api { } } +/// Represents a collection of [Method]s that are wrappers for Event +class AstEventChannelApi extends Api { + /// Parametric constructor for [AstEventChannelApi]. + AstEventChannelApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + }); + + @override + String toString() { + return '(EventChannelApi name:$name methods:$methods documentationComments:$documentationComments)'; + } +} + /// Represents a constructor for an API. class Constructor extends Method { /// Parametric constructor for [Constructor]. @@ -680,6 +695,9 @@ class Class extends Node { Class({ required this.name, required this.fields, + this.superClassName, + this.superClass, + this.isSealed = false, this.isReferenced = true, this.isSwiftClass = false, this.documentationComments = const [], @@ -691,6 +709,20 @@ class Class extends Node { /// All the fields contained in the class. List fields; + /// Name of parent class, will be empty when there is no super class. + String? superClassName; + + /// The definition of the parent class. + Class? superClass; + + /// List of class definitions of children. + /// + /// This is only meant to be used by sealed classes used in event channel methods. + List children = []; + + /// Whether the class is sealed. + bool isSealed; + /// Whether the class is referenced in any API. bool isReferenced; @@ -709,7 +741,7 @@ class Class extends Node { @override String toString() { - return '(Class name:$name fields:$fields documentationComments:$documentationComments)'; + return '(Class name:$name fields:$fields superClass:$superClassName children:$children isSealed:$isSealed isReferenced:$isReferenced documentationComments:$documentationComments)'; } } @@ -772,6 +804,10 @@ class Root extends Node { required this.classes, required this.apis, required this.enums, + this.containsHostApi = false, + this.containsFlutterApi = false, + this.containsProxyApi = false, + this.containsEventChannel = false, }); /// Factory function for generating an empty root, usually used when early errors are encountered. @@ -788,10 +824,25 @@ class Root extends Node { /// All of the enums contained in the AST. List enums; + /// Whether the root has any Host API definitions. + bool containsHostApi; + + /// Whether the root has any Flutter API definitions. + bool containsFlutterApi; + + /// Whether the root has any Proxy API definitions. + bool containsProxyApi; + + /// Whether the root has any event channel definitions. + bool containsEventChannel; + /// Returns true if the number of custom types would exceed the available enumerations /// on the standard codec. bool get requiresOverflowClass => - classes.length + enums.length >= totalCustomCodecKeysAllowed; + classes.length - _numberOfSealedClasses() + enums.length >= + totalCustomCodecKeysAllowed; + + int _numberOfSealedClasses() => classes.where((Class c) => c.isSealed).length; @override String toString() { diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index 96579cc23f9a..d911da474220 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -213,15 +213,8 @@ class CppHeaderGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasFlutterApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - _writeFlutterError(indent); - if (hasHostApi) { + if (root.containsHostApi) { _writeErrorOr( indent, friends: root.apis @@ -229,9 +222,6 @@ class CppHeaderGenerator extends StructuredGenerator { .map((Api api) => api.name), ); } - if (hasFlutterApi) { - // Nothing yet. - } } @override @@ -1002,7 +992,7 @@ EncodableValue $_overflowClassName::FromEncodableList( required String dartPackageName, }) { final List enumeratedTypes = - getEnumeratedTypes(root).toList(); + getEnumeratedTypes(root, excludeSealedClasses: true).toList(); indent.newln(); if (root.requiresOverflowClass) { _writeCodecOverflowUtilities( diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index e8c7550cd708..abacf3dea31c 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -30,7 +30,10 @@ const DocumentCommentSpecification _docCommentSpec = DocumentCommentSpecification(_docCommentPrefix); /// The custom codec used for all pigeon APIs. -const String _pigeonCodec = '_PigeonCodec'; +const String _pigeonMessageCodec = '_PigeonCodec'; + +/// Name of field used for host API codec. +const String _pigeonMethodChannelCodec = 'pigeonMethodCodec'; const String _overflowClassName = '_PigeonCodecOverflow'; @@ -118,11 +121,10 @@ class DartGenerator extends StructuredGenerator { ); indent.newln(); - final bool hasProxyApi = root.apis.any((Api api) => api is AstProxyApi); indent.writeln( - "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer${hasProxyApi ? ', immutable, protected' : ''};"); + "import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer${root.containsProxyApi ? ', immutable, protected' : ''};"); indent.writeln("import 'package:flutter/services.dart';"); - if (hasProxyApi) { + if (root.containsProxyApi) { indent.writeln( "import 'package:flutter/widgets.dart' show WidgetsFlutterBinding;", ); @@ -161,9 +163,16 @@ class DartGenerator extends StructuredGenerator { indent.newln(); addDocumentationComments( indent, classDefinition.documentationComments, _docCommentSpec); + final String sealed = classDefinition.isSealed ? 'sealed ' : ''; + final String implements = classDefinition.superClassName != null + ? 'extends ${classDefinition.superClassName} ' + : ''; - indent.write('class ${classDefinition.name} '); + indent.write('${sealed}class ${classDefinition.name} $implements'); indent.addScoped('{', '}', () { + if (classDefinition.fields.isEmpty) { + return; + } _writeConstructor(indent, classDefinition); indent.newln(); for (final NamedType field @@ -285,10 +294,12 @@ class DartGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - void writeEncodeLogic(EnumeratedType customType) { + void writeEncodeLogic( + EnumeratedType customType, int nonSerializedClassCount) { indent.writeScoped('else if (value is ${customType.name}) {', '}', () { - if (customType.enumeration < maximumCodecFieldKey) { - indent.writeln('buffer.putUint8(${customType.enumeration});'); + if (customType.offset(nonSerializedClassCount) < maximumCodecFieldKey) { + indent.writeln( + 'buffer.putUint8(${customType.offset(nonSerializedClassCount)});'); if (customType.type == CustomTypes.customClass) { indent.writeln('writeValue(buffer, value.encode());'); } else if (customType.type == CustomTypes.customEnum) { @@ -299,18 +310,20 @@ class DartGenerator extends StructuredGenerator { ? '.encode()' : '.index'; indent.writeln( - 'final $_overflowClassName wrap = $_overflowClassName(type: ${customType.enumeration - maximumCodecFieldKey}, wrapped: value$encodeString);'); + 'final $_overflowClassName wrap = $_overflowClassName(type: ${customType.offset(nonSerializedClassCount) - maximumCodecFieldKey}, wrapped: value$encodeString);'); indent.writeln('buffer.putUint8($maximumCodecFieldKey);'); indent.writeln('writeValue(buffer, wrap.encode());'); } }, addTrailingNewline: false); } - void writeDecodeLogic(EnumeratedType customType) { - indent.writeln('case ${customType.enumeration}: '); + void writeDecodeLogic( + EnumeratedType customType, int nonSerializedClassCount) { + indent.writeln('case ${customType.offset(nonSerializedClassCount)}: '); indent.nest(1, () { if (customType.type == CustomTypes.customClass) { - if (customType.enumeration == maximumCodecFieldKey) { + if (customType.offset(nonSerializedClassCount) == + maximumCodecFieldKey) { indent.writeln( 'final ${customType.name} wrapper = ${customType.name}.decode(readValue(buffer)!);'); indent.writeln('return wrapper.unwrap();'); @@ -331,14 +344,14 @@ class DartGenerator extends StructuredGenerator { indent.newln(); final List enumeratedTypes = - getEnumeratedTypes(root).toList(); + getEnumeratedTypes(root, excludeSealedClasses: true).toList(); if (root.requiresOverflowClass) { _writeCodecOverflowUtilities(indent, enumeratedTypes); } indent.newln(); - indent.write('class $_pigeonCodec extends StandardMessageCodec'); + indent.write('class $_pigeonMessageCodec extends StandardMessageCodec'); indent.addScoped(' {', '}', () { - indent.writeln('const $_pigeonCodec();'); + indent.writeln('const $_pigeonMessageCodec();'); indent.writeln('@override'); indent.write('void writeValue(WriteBuffer buffer, Object? value) '); indent.addScoped('{', '}', () { @@ -346,10 +359,14 @@ class DartGenerator extends StructuredGenerator { indent.writeln('buffer.putUint8(4);'); indent.writeln('buffer.putInt64(value);'); }, addTrailingNewline: false); - + int nonSerializedClassCount = 0; enumerate(enumeratedTypes, (int index, final EnumeratedType customType) { - writeEncodeLogic(customType); + if (customType.associatedClass?.isSealed ?? false) { + nonSerializedClassCount += 1; + return; + } + writeEncodeLogic(customType, nonSerializedClassCount); }); indent.addScoped(' else {', '}', () { indent.writeln('super.writeValue(buffer, value);'); @@ -361,13 +378,17 @@ class DartGenerator extends StructuredGenerator { indent.addScoped('{', '}', () { indent.write('switch (type) '); indent.addScoped('{', '}', () { + int nonSerializedClassCount = 0; for (final EnumeratedType customType in enumeratedTypes) { - if (customType.enumeration < maximumCodecFieldKey) { - writeDecodeLogic(customType); + if (customType.associatedClass?.isSealed ?? false) { + nonSerializedClassCount++; + } else if (customType.offset(nonSerializedClassCount) < + maximumCodecFieldKey) { + writeDecodeLogic(customType, nonSerializedClassCount); } } if (root.requiresOverflowClass) { - writeDecodeLogic(overflowClass); + writeDecodeLogic(overflowClass, 0); } indent.writeln('default:'); indent.nest(1, () { @@ -376,6 +397,11 @@ class DartGenerator extends StructuredGenerator { }); }); }); + if (root.containsEventChannel) { + indent.newln(); + indent.writeln( + 'const StandardMethodCodec $_pigeonMethodChannelCodec = StandardMethodCodec($_pigeonMessageCodec());'); + } } /// Writes the code for host [Api], [api]. @@ -408,7 +434,7 @@ class DartGenerator extends StructuredGenerator { 'static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance;'); } indent.writeln( - 'static const MessageCodec $_pigeonChannelCodec = $_pigeonCodec();'); + 'static const MessageCodec $_pigeonChannelCodec = $_pigeonMessageCodec();'); indent.newln(); for (final Method func in api.methods) { addDocumentationComments( @@ -489,7 +515,7 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; '''); indent.writeln( - 'static const MessageCodec $_pigeonChannelCodec = $_pigeonCodec();'); + 'static const MessageCodec $_pigeonChannelCodec = $_pigeonMessageCodec();'); indent.newln(); indent.writeln('final String $_suffixVarName;'); indent.newln(); @@ -512,6 +538,33 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; }); } + @override + void writeEventChannelApi( + DartOptions generatorOptions, + Root root, + Indent indent, + AstEventChannelApi api, { + required String dartPackageName, + }) { + indent.newln(); + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + for (final Method func in api.methods) { + indent.format(''' + Stream<${func.returnType.baseName}> ${func.name}(${_getMethodParameterSignature(func.parameters, addTrailingComma: true)} {String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.\$instanceName'; + } + const EventChannel ${func.name}Channel = + EventChannel('${makeChannelName(api, func, dartPackageName)}', $_pigeonMethodChannelCodec); + return ${func.name}Channel.receiveBroadcastStream().map((dynamic event) { + return event as ${func.returnType.baseName}; + }); + } + '''); + } + } + @override void writeInstanceManager( DartOptions generatorOptions, @@ -582,7 +635,7 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; ..type = cb.refer('MessageCodec') ..static = true ..modifier = cb.FieldModifier.constant - ..assignment = const cb.Code('$_pigeonCodec()'); + ..assignment = const cb.Code('$_pigeonMessageCodec()'); }, ) ], @@ -851,7 +904,7 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; /// Generates Dart source code for test support libraries based on the given AST /// represented by [root], outputting the code to [sink]. [sourceOutPath] is the - /// path of the generated dart code to be tested. [testOutPath] is where the + /// path of the generated Dart code to be tested. [testOutPath] is where the /// test code will be generated. void generateTest( DartOptions generatorOptions, @@ -938,22 +991,12 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; Indent indent, { required String dartPackageName, }) { - final bool hasHostMethod = root.apis - .whereType() - .any((AstHostApi api) => api.methods.isNotEmpty) || - root.apis.whereType().any((AstProxyApi api) => - api.constructors.isNotEmpty || - api.attachedFields.isNotEmpty || - api.hostMethods.isNotEmpty); - final bool hasFlutterMethod = root.apis - .whereType() - .any((AstFlutterApi api) => api.methods.isNotEmpty) || - root.apis.any((Api api) => api is AstProxyApi); - - if (hasHostMethod) { + if (root.containsHostApi || root.containsProxyApi) { _writeCreateConnectionError(indent); } - if (hasFlutterMethod || generatorOptions.testOutPath != null) { + if (root.containsFlutterApi || + root.containsProxyApi || + generatorOptions.testOutPath != null) { _writeWrapResponse(generatorOptions, root, indent); } } @@ -1015,16 +1058,22 @@ if (wrapped == null) { } '''); indent.writeScoped('switch (type) {', '}', () { + int nonSerializedClassCount = 0; for (int i = totalCustomCodecKeysAllowed; i < types.length; i++) { - indent.writeScoped('case ${i - totalCustomCodecKeysAllowed}:', '', - () { - if (types[i].type == CustomTypes.customClass) { - indent.writeln('return ${types[i].name}.decode(wrapped!);'); - } else if (types[i].type == CustomTypes.customEnum) { - indent.writeln( - 'return ${types[i].name}.values[wrapped! as int];'); - } - }); + if (types[i].associatedClass?.isSealed ?? false) { + nonSerializedClassCount++; + } else { + indent.writeScoped( + 'case ${i - nonSerializedClassCount - totalCustomCodecKeysAllowed}:', + '', () { + if (types[i].type == CustomTypes.customClass) { + indent.writeln('return ${types[i].name}.decode(wrapped!);'); + } else if (types[i].type == CustomTypes.customEnum) { + indent.writeln( + 'return ${types[i].name}.values[wrapped! as int];'); + } + }); + } } }); indent.writeln('return null;'); @@ -2095,7 +2144,10 @@ String _getParameterName(int count, NamedType field) => /// Generates the parameters code for [func] /// Example: (func, _getParameterName) -> 'String? foo, int bar' -String _getMethodParameterSignature(Iterable parameters) { +String _getMethodParameterSignature( + Iterable parameters, { + bool addTrailingComma = false, +}) { String signature = ''; if (parameters.isEmpty) { return signature; @@ -2145,8 +2197,10 @@ String _getMethodParameterSignature(Iterable parameters) { return '$baseParams[$optionalParameterString$trailingComma]'; } if (namedParams.isNotEmpty) { - final String trailingComma = - requiredPositionalParams.length + namedParams.length > 2 ? ',' : ''; + final String trailingComma = addTrailingComma || + requiredPositionalParams.length + namedParams.length > 2 + ? ', ' + : ''; return '$baseParams{$namedParameterString$trailingComma}'; } return signature; diff --git a/packages/pigeon/lib/generator.dart b/packages/pigeon/lib/generator.dart index 952b80e89872..fb4e2408beb6 100644 --- a/packages/pigeon/lib/generator.dart +++ b/packages/pigeon/lib/generator.dart @@ -282,6 +282,14 @@ abstract class StructuredGenerator extends Generator { api, dartPackageName: dartPackageName, ); + case AstEventChannelApi(): + writeEventChannelApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); } } } @@ -345,4 +353,13 @@ abstract class StructuredGenerator extends Generator { AstProxyApi api, { required String dartPackageName, }) {} + + /// Writes a single event channel Api to [indent]. + void writeEventChannelApi( + T generatorOptions, + Root root, + Indent indent, + AstEventChannelApi api, { + required String dartPackageName, + }) {} } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 117cf8d6d946..4afdc4b1be70 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/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 = '22.6.2'; +const String pigeonVersion = '22.7.0'; /// Read all the content from [stdin] to a String. String readStdin() { @@ -432,6 +432,9 @@ class EnumeratedType { /// The associated Enum that is represented by the [EnumeratedType]. final Enum? associatedEnum; + + /// Returns the offset of the enumeration. + int offset(int offset) => enumeration - offset; } /// Supported basic datatypes. @@ -605,7 +608,10 @@ enum CustomTypes { /// Return the enumerated types that must exist in the codec /// where the enumeration should be the key used in the buffer. -Iterable getEnumeratedTypes(Root root) sync* { +Iterable getEnumeratedTypes( + Root root, { + bool excludeSealedClasses = false, +}) sync* { int index = 0; for (final Enum customEnum in root.enums) { @@ -619,13 +625,15 @@ Iterable getEnumeratedTypes(Root root) sync* { } for (final Class customClass in root.classes) { - yield EnumeratedType( - customClass.name, - index + minimumCodecFieldKey, - CustomTypes.customClass, - associatedClass: customClass, - ); - index += 1; + if (!excludeSealedClasses || !customClass.isSealed) { + yield EnumeratedType( + customClass.name, + index + minimumCodecFieldKey, + CustomTypes.customClass, + associatedClass: customClass, + ); + index += 1; + } } } @@ -656,7 +664,7 @@ class DocumentCommentSpecification { /// Formats documentation comments and adds them to current Indent. /// -/// The [comments] list is meant for comments written in the input dart file. +/// The [comments] list is meant for comments written in the input Dart file. /// The [generatorComments] list is meant for comments added by the generators. /// Include white space for all tokens when called, no assumptions are made. void addDocumentationComments( @@ -674,7 +682,7 @@ void addDocumentationComments( /// Formats documentation comments and adds them to current Indent. /// -/// The [comments] list is meant for comments written in the input dart file. +/// The [comments] list is meant for comments written in the input Dart file. /// The [generatorComments] list is meant for comments added by the generators. /// Include white space for all tokens when called, no assumptions are made. Iterable asDocumentationComments( @@ -801,6 +809,24 @@ String toUpperCamelCase(String text) { }).join(); } +/// Converts strings to Lower Camel Case. +String toLowerCamelCase(String text) { + final RegExp separatorPattern = RegExp(r'[ _-]'); + bool firstWord = true; + return text.split(separatorPattern).map((String word) { + if (word.isEmpty) { + return ''; + } + if (firstWord) { + firstWord = false; + return word.substring(0, 1).toLowerCase() + word.substring(1); + } + return word.isEmpty + ? '' + : word.substring(0, 1).toUpperCase() + word.substring(1); + }).join(); +} + /// Converts string to SCREAMING_SNAKE_CASE. String toScreamingSnakeCase(String string) { return string diff --git a/packages/pigeon/lib/gobject_generator.dart b/packages/pigeon/lib/gobject_generator.dart index 04f4bd1afa8d..486b6143986e 100644 --- a/packages/pigeon/lib/gobject_generator.dart +++ b/packages/pigeon/lib/gobject_generator.dart @@ -959,7 +959,8 @@ class GObjectSourceGenerator extends StructuredGenerator { final String codecClassName = _getClassName(module, _codecBaseName); final String codecMethodPrefix = _getMethodPrefix(module, _codecBaseName); - final Iterable customTypes = getEnumeratedTypes(root); + final Iterable customTypes = + getEnumeratedTypes(root, excludeSealedClasses: true); indent.newln(); _writeObjectStruct(indent, module, _codecBaseName, () {}, @@ -2012,7 +2013,7 @@ String _referenceValue(String module, TypeDeclaration type, String variableName, } int _getTypeEnumeration(Root root, TypeDeclaration type) { - return getEnumeratedTypes(root) + return getEnumeratedTypes(root, excludeSealedClasses: true) .firstWhere((EnumeratedType t) => (type.isClass && t.associatedClass == type.associatedClass) || (type.isEnum && t.associatedEnum == type.associatedEnum)) diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index 27a1a5915e78..938b20c2a057 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -458,7 +458,7 @@ class JavaGenerator extends StructuredGenerator { required String dartPackageName, }) { final List enumeratedTypes = - getEnumeratedTypes(root).toList(); + getEnumeratedTypes(root, excludeSealedClasses: true).toList(); void writeEncodeLogic(EnumeratedType customType) { final String encodeString = @@ -1117,20 +1117,13 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasFlutterApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - indent.newln(); _writeErrorClass(indent); - if (hasHostApi) { + if (root.containsHostApi) { indent.newln(); _writeWrapError(indent); } - if (hasFlutterApi) { + if (root.containsFlutterApi) { indent.newln(); _writeCreateConnectionError(indent); } diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index 3b5ff62aa822..f0a185cdcc72 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -30,6 +30,9 @@ const DocumentCommentSpecification _docCommentSpec = String _codecName = 'PigeonCodec'; +/// Name of field used for host API codec. +const String _pigeonMethodChannelCodec = 'PigeonMethodCodec'; + const String _overflowClassName = '${classNamePrefix}CodecOverflow'; /// Options that control how Kotlin code will be generated. @@ -147,7 +150,9 @@ class KotlinGenerator extends StructuredGenerator { indent.writeln('import android.util.Log'); indent.writeln('import io.flutter.plugin.common.BasicMessageChannel'); indent.writeln('import io.flutter.plugin.common.BinaryMessenger'); + indent.writeln('import io.flutter.plugin.common.EventChannel'); indent.writeln('import io.flutter.plugin.common.MessageCodec'); + indent.writeln('import io.flutter.plugin.common.StandardMethodCodec'); indent.writeln('import io.flutter.plugin.common.StandardMessageCodec'); indent.writeln('import java.io.ByteArrayOutputStream'); indent.writeln('import java.nio.ByteBuffer'); @@ -197,14 +202,21 @@ class KotlinGenerator extends StructuredGenerator { Class classDefinition, { required String dartPackageName, }) { - const List generatedMessages = [ + final List generatedMessages = [ ' Generated class from Pigeon that represents data sent in messages.' ]; + if (classDefinition.isSealed) { + generatedMessages.add( + ' This class should not be extended by any user class outside of the generated file.'); + } indent.newln(); addDocumentationComments( indent, classDefinition.documentationComments, _docCommentSpec, generatorComments: generatedMessages); _writeDataClassSignature(indent, classDefinition); + if (classDefinition.isSealed) { + return; + } indent.addScoped(' {', '}', () { writeClassDecode( generatorOptions, @@ -228,9 +240,16 @@ class KotlinGenerator extends StructuredGenerator { Class classDefinition, { bool private = false, }) { - indent.write( - '${private ? 'private ' : ''}data class ${classDefinition.name} '); - indent.addScoped('(', ')', () { + final String privateString = private ? 'private ' : ''; + final String classType = classDefinition.isSealed ? 'sealed' : 'data'; + final String inheritance = classDefinition.superClass != null + ? ' : ${classDefinition.superClassName}()' + : ''; + indent.write('$privateString$classType class ${classDefinition.name} '); + if (classDefinition.isSealed) { + return; + } + indent.addScoped('(', ')$inheritance', () { for (final NamedType element in getFieldsInSerializationOrder(classDefinition)) { _writeClassField(indent, element); @@ -334,7 +353,7 @@ class KotlinGenerator extends StructuredGenerator { required String dartPackageName, }) { final List enumeratedTypes = - getEnumeratedTypes(root).toList(); + getEnumeratedTypes(root, excludeSealedClasses: true).toList(); void writeEncodeLogic(EnumeratedType customType) { final String encodeString = @@ -427,6 +446,11 @@ class KotlinGenerator extends StructuredGenerator { }); }); indent.newln(); + if (root.containsEventChannel) { + indent.writeln( + 'val ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodChannelCodec = StandardMethodCodec(${generatorOptions.fileSpecificClassNameComponent}$_codecName());'); + indent.newln(); + } } void _writeCodecOverflowUtilities( @@ -984,6 +1008,72 @@ if (wrapped == null) { ); } + @override + void writeEventChannelApi( + KotlinOptions generatorOptions, + Root root, + Indent indent, + AstEventChannelApi api, { + required String dartPackageName, + }) { + indent.newln(); + indent.format(''' + private class PigeonStreamHandler( + val wrapper: PigeonEventChannelWrapper + ) : EventChannel.StreamHandler { + var pigeonSink: PigeonEventSink? = null + + override fun onListen(p0: Any?, sink: EventChannel.EventSink) { + pigeonSink = PigeonEventSink(sink) + wrapper.onListen(p0, pigeonSink!!) + } + + override fun onCancel(p0: Any?) { + pigeonSink = null + wrapper.onCancel(p0) + } + } + + interface PigeonEventChannelWrapper { + open fun onListen(p0: Any?, sink: PigeonEventSink) {} + + open fun onCancel(p0: Any?) {} + } + + class PigeonEventSink(private val sink: EventChannel.EventSink) { + fun success(value: T) { + sink.success(value) + } + + fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + sink.error(errorCode, errorMessage, errorDetails) + } + + fun endOfStream() { + sink.endOfStream() + } + } + '''); + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + for (final Method func in api.methods) { + indent.format(''' + abstract class ${toUpperCamelCase(func.name)}StreamHandler : 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) + EventChannel(messenger, channelName, ${generatorOptions.fileSpecificClassNameComponent}$_pigeonMethodChannelCodec).setStreamHandler(internalStreamHandler) + } + } + } + '''); + } + } + void _writeWrapResult(Indent indent) { indent.newln(); indent.write('private fun wrapResult(result: Any?): List '); @@ -1054,19 +1144,11 @@ if (wrapped == null) { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasFlutterApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasProxyApi = root.apis.any((Api api) => api is AstProxyApi); - - if (hasHostApi || hasProxyApi) { + if (root.containsHostApi || root.containsProxyApi) { _writeWrapResult(indent); _writeWrapError(generatorOptions, indent); } - if (hasFlutterApi || hasProxyApi) { + if (root.containsFlutterApi || root.containsProxyApi) { _writeCreateConnectionError(generatorOptions, indent); } if (generatorOptions.includeErrorClass) { diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index d0e0e99ee293..ed6bd37ac760 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -695,7 +695,7 @@ if (self.wrapped == nil) { }) { const String codecName = 'PigeonCodec'; final List enumeratedTypes = - getEnumeratedTypes(root).toList(); + getEnumeratedTypes(root, excludeSealedClasses: true).toList(); final String readerWriterName = '${generatorOptions.prefix}${toUpperCamelCase(generatorOptions.fileSpecificClassNameComponent ?? '')}${codecName}ReaderWriter'; final String readerName = @@ -901,23 +901,16 @@ if (self.wrapped == nil) { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasFlutterApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - - if (hasHostApi) { + if (root.containsHostApi) { _writeWrapError(indent); indent.newln(); } - if (hasFlutterApi) { + if (root.containsFlutterApi) { _writeCreateConnectionError(indent); indent.newln(); } - if (hasHostApi || hasFlutterApi) { + if (root.containsHostApi || root.containsFlutterApi) { _writeGetNullableObjectAtIndex(indent); } diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 075049d98298..02347efa0a7b 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -160,6 +160,12 @@ class ProxyApi { final KotlinProxyApiOptions? kotlinOptions; } +/// Metadata to annotate a pigeon API that contains event channels. +class EventChannelApi { + /// Constructor. + const EventChannelApi(); +} + /// Metadata to annotation methods to control the selector used for objc output. /// The number of components in the provided selector must match the number of /// arguments in the annotated method. @@ -278,10 +284,10 @@ class PigeonOptions { /// Path to the file which will be processed. final String? input; - /// Path to the dart file that will be generated. + /// Path to the Dart file that will be generated. final String? dartOut; - /// Path to the dart file that will be generated for test support classes. + /// Path to the Dart file that will be generated for test support classes. final String? dartTestOut; /// Path to the ".h" Objective-C file will be generated. @@ -537,6 +543,29 @@ DartOptions _dartOptionsWithCopyrightHeader( )); } +void _errorOnEventChannelApi(List errors, String generator, Root root) { + if (root.containsEventChannel) { + errors.add(Error(message: '$generator does not support event channels')); + } +} + +void _errorOnSealedClass(List errors, String generator, Root root) { + if (root.classes.any( + (Class element) => element.isSealed, + )) { + errors.add(Error(message: '$generator does not support sealed classes')); + } +} + +void _errorOnInheritedClass(List errors, String generator, Root root) { + if (root.classes.any( + (Class element) => element.superClass != null, + )) { + errors.add( + Error(message: '$generator does not support inheritance in classes')); + } +} + /// A [GeneratorAdapter] that generates the AST. class AstGeneratorAdapter implements GeneratorAdapter { /// Constructor for [AstGeneratorAdapter]. @@ -564,6 +593,9 @@ class DartGeneratorAdapter implements GeneratorAdapter { /// Constructor for [DartGeneratorAdapter]. DartGeneratorAdapter(); + /// A string representing the name of the language being generated. + String languageString = 'Dart'; + @override List fileTypeList = const [FileType.na]; @@ -644,6 +676,9 @@ class ObjcGeneratorAdapter implements GeneratorAdapter { ObjcGeneratorAdapter( {this.fileTypeList = const [FileType.header, FileType.source]}); + /// A string representing the name of the language being generated. + String languageString = 'Objective-C'; + @override List fileTypeList; @@ -685,7 +720,13 @@ class ObjcGeneratorAdapter implements GeneratorAdapter { } @override - List validate(PigeonOptions options, Root root) => []; + List validate(PigeonOptions options, Root root) { + final List errors = []; + _errorOnEventChannelApi(errors, languageString, root); + _errorOnSealedClass(errors, languageString, root); + _errorOnInheritedClass(errors, languageString, root); + return errors; + } } /// A [GeneratorAdapter] that generates Java source code. @@ -693,6 +734,9 @@ class JavaGeneratorAdapter implements GeneratorAdapter { /// Constructor for [JavaGeneratorAdapter]. JavaGeneratorAdapter(); + /// A string representing the name of the language being generated. + String languageString = 'Java'; + @override List fileTypeList = const [FileType.na]; @@ -722,7 +766,13 @@ class JavaGeneratorAdapter implements GeneratorAdapter { _openSink(options.javaOut, basePath: options.basePath ?? ''); @override - List validate(PigeonOptions options, Root root) => []; + List validate(PigeonOptions options, Root root) { + final List errors = []; + _errorOnEventChannelApi(errors, languageString, root); + _errorOnSealedClass(errors, languageString, root); + _errorOnInheritedClass(errors, languageString, root); + return errors; + } } /// A [GeneratorAdapter] that generates Swift source code. @@ -730,6 +780,9 @@ class SwiftGeneratorAdapter implements GeneratorAdapter { /// Constructor for [SwiftGeneratorAdapter]. SwiftGeneratorAdapter(); + /// A string representing the name of the language being generated. + String languageString = 'Swift'; + @override List fileTypeList = const [FileType.na]; @@ -770,6 +823,9 @@ class CppGeneratorAdapter implements GeneratorAdapter { CppGeneratorAdapter( {this.fileTypeList = const [FileType.header, FileType.source]}); + /// A string representing the name of the language being generated. + String languageString = 'C++'; + @override List fileTypeList; @@ -805,7 +861,13 @@ class CppGeneratorAdapter implements GeneratorAdapter { } @override - List validate(PigeonOptions options, Root root) => []; + List validate(PigeonOptions options, Root root) { + final List errors = []; + _errorOnEventChannelApi(errors, languageString, root); + _errorOnSealedClass(errors, languageString, root); + _errorOnInheritedClass(errors, languageString, root); + return errors; + } } /// A [GeneratorAdapter] that generates GObject source code. @@ -814,6 +876,9 @@ class GObjectGeneratorAdapter implements GeneratorAdapter { GObjectGeneratorAdapter( {this.fileTypeList = const [FileType.header, FileType.source]}); + /// A string representing the name of the language being generated. + String languageString = 'GObject'; + @override List fileTypeList; @@ -862,6 +927,10 @@ class GObjectGeneratorAdapter implements GeneratorAdapter { message: 'GObject generator does not yet support more than $totalCustomCodecKeysAllowed custom types.')); } + _errorOnEventChannelApi(errors, languageString, root); + _errorOnSealedClass(errors, languageString, root); + _errorOnInheritedClass(errors, languageString, root); + return errors; } } @@ -986,6 +1055,24 @@ List _validateAst(Root root, String source) { lineNumber: _calculateLineNumberNullable(source, field.offset), )); } + if (classDefinition.isSealed) { + if (classDefinition.fields.isNotEmpty) { + result.add(Error( + message: + 'Sealed class: "${classDefinition.name}" must not contain fields.', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } + if (classDefinition.superClass != null) { + if (!classDefinition.superClass!.isSealed) { + result.add(Error( + message: + 'Child class: "${classDefinition.name}" must extend a sealed class.', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } } } @@ -1020,6 +1107,13 @@ List _validateAst(Root root, String source) { lineNumber: _calculateLineNumberNullable(source, method.offset), )); } + if (api is AstEventChannelApi && method.parameters.isNotEmpty) { + result.add(Error( + message: + 'event channel methods must not be contain parameters, in method "${method.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); + } for (final Parameter param in method.parameters) { if (param.type.baseName.isEmpty) { result.add(Error( @@ -1387,20 +1481,43 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { getReferencedTypes(_apis, _classes); final Set referencedTypeNames = referencedTypes.keys.map((TypeDeclaration e) => e.baseName).toSet(); - final List nonReferencedClasses = List.from(_classes); - nonReferencedClasses + final List nonReferencedTypes = List.from(_classes); + nonReferencedTypes .removeWhere((Class x) => referencedTypeNames.contains(x.name)); - for (final Class x in nonReferencedClasses) { + for (final Class x in nonReferencedTypes) { x.isReferenced = false; } final List referencedEnums = List.from(_enums); - final Root completeRoot = - Root(apis: _apis, classes: _classes, enums: referencedEnums); + bool containsHostApi = false; + bool containsFlutterApi = false; + bool containsProxyApi = false; + bool containsEventChannel = false; + + for (final Api api in _apis) { + switch (api) { + case AstHostApi(): + containsHostApi = true; + case AstFlutterApi(): + containsFlutterApi = true; + case AstProxyApi(): + containsProxyApi = true; + case AstEventChannelApi(): + containsEventChannel = true; + } + } + + final Root completeRoot = Root( + apis: _apis, + classes: _classes, + enums: referencedEnums, + containsHostApi: containsHostApi, + containsFlutterApi: containsFlutterApi, + containsProxyApi: containsProxyApi, + containsEventChannel: containsEventChannel, + ); - final List validateErrors = _validateAst(completeRoot, source); final List totalErrors = List.from(_errors); - totalErrors.addAll(validateErrors); for (final MapEntry> element in referencedTypes.entries) { @@ -1429,6 +1546,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { classDefinition.fields = _attachAssociatedDefinitions( classDefinition.fields, ); + classDefinition.superClass = _attachSuperClass(classDefinition); } for (final Api api in _apis) { @@ -1456,6 +1574,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { api.interfaces = newInterfaceSet; } } + final List validateErrors = _validateAst(completeRoot, source); + totalErrors.addAll(validateErrors); return ParseResults( root: totalErrors.isEmpty @@ -1503,6 +1623,20 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { return result; } + Class? _attachSuperClass(Class childClass) { + if (childClass.superClassName == null) { + return null; + } + + for (final Class parentClass in _classes) { + if (parentClass.name == childClass.superClassName) { + parentClass.children.add(childClass); + return parentClass; + } + } + return null; + } + Object _expressionToMap(dart_ast.Expression expression) { if (expression is dart_ast.MethodInvocation) { final Map result = {}; @@ -1599,6 +1733,14 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { _storeCurrentClass(); if (node.abstractKeyword != null) { + if (node.metadata.length > 2 || + (node.metadata.length > 1 && + !_hasMetadata(node.metadata, 'ConfigurePigeon'))) { + _errors.add(Error( + message: + 'API "${node.name.lexeme}" can only have one API annotation but contains: ${node.metadata}', + lineNumber: _calculateLineNumber(source, node.offset))); + } if (_hasMetadata(node.metadata, 'HostApi')) { final dart_ast.Annotation hostApi = node.metadata.firstWhere( (dart_ast.Annotation element) => element.name.name == 'HostApi'); @@ -1736,11 +1878,22 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); + } else if (_hasMetadata(node.metadata, 'EventChannelApi')) { + _currentApi = AstEventChannelApi( + name: node.name.lexeme, + methods: [], + documentationComments: + _documentationCommentsParser(node.documentationComment?.tokens), + ); } } else { _currentClass = Class( name: node.name.lexeme, fields: [], + superClassName: + node.implementsClause?.interfaces.first.name2.toString() ?? + node.extendsClause?.superclass.name2.toString(), + isSealed: node.sealedKeyword != null, isSwiftClass: _hasMetadata(node.metadata, 'SwiftClass'), documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), @@ -1889,6 +2042,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { AstHostApi() => ApiLocation.host, AstProxyApi() => ApiLocation.host, AstFlutterApi() => ApiLocation.flutter, + AstEventChannelApi() => ApiLocation.host, }, isAsynchronous: isAsynchronous, objcSelector: objcSelector, diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 289c50dbd887..e188719f9dd3 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -204,13 +204,13 @@ class SwiftGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final String codecName = _getCodecName(generatorOptions); + final String codecName = _getMessageCodecName(generatorOptions); final String readerWriterName = '${codecName}ReaderWriter'; final String readerName = '${codecName}Reader'; final String writerName = '${codecName}Writer'; final List enumeratedTypes = - getEnumeratedTypes(root).toList(); + getEnumeratedTypes(root, excludeSealedClasses: true).toList(); void writeDecodeLogic(EnumeratedType customType) { indent.writeln('case ${customType.enumeration}:'); @@ -332,6 +332,11 @@ class SwiftGenerator extends StructuredGenerator { 'static let shared = $codecName(readerWriter: $readerWriterName())'); }); indent.newln(); + if (root.containsEventChannel) { + indent.writeln( + 'var ${_getMethodCodecVarName(generatorOptions)} = FlutterStandardMethodCodec(readerWriter: $readerWriterName());'); + indent.newln(); + } } void _writeDataClassSignature( @@ -340,10 +345,17 @@ class SwiftGenerator extends StructuredGenerator { bool private = false, }) { final String privateString = private ? 'private ' : ''; + final String extendsString = classDefinition.superClass != null + ? ': ${classDefinition.superClass!.name}' + : ''; if (classDefinition.isSwiftClass) { - indent.write('${privateString}class ${classDefinition.name} '); + indent.write( + '${privateString}class ${classDefinition.name}$extendsString '); + } else if (classDefinition.isSealed) { + indent.write('protocol ${classDefinition.name} '); } else { - indent.write('${privateString}struct ${classDefinition.name} '); + indent.write( + '${privateString}struct ${classDefinition.name}$extendsString '); } indent.addScoped('{', '', () { @@ -361,7 +373,7 @@ class SwiftGenerator extends StructuredGenerator { _writeClassField(indent, field, addNil: !classDefinition.isSwiftClass); indent.newln(); } - }); + }, addTrailingNewline: false); } void _writeCodecOverflowUtilities( @@ -444,15 +456,22 @@ if (wrapped == nil) { Class classDefinition, { required String dartPackageName, }) { - const List generatedComments = [ + final List generatedComments = [ ' Generated class from Pigeon that represents data sent in messages.' ]; + if (classDefinition.isSealed) { + generatedComments.add( + ' This class should not be extended by any user class outside of the generated file.'); + } indent.newln(); addDocumentationComments( indent, classDefinition.documentationComments, _docCommentSpec, generatorComments: generatedComments); _writeDataClassSignature(indent, classDefinition); indent.writeScoped('', '}', () { + if (classDefinition.isSealed) { + return; + } indent.newln(); writeClassDecode( generatorOptions, @@ -639,7 +658,7 @@ if (wrapped == nil) { indent.writeln( r'self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""'); }); - final String codecName = _getCodecName(generatorOptions); + final String codecName = _getMessageCodecName(generatorOptions); indent.write('var codec: $codecName '); indent.addScoped('{', '}', () { indent.writeln('return $codecName.shared'); @@ -705,7 +724,7 @@ if (wrapped == nil) { indent.write('class ${apiName}Setup '); indent.addScoped('{', '}', () { indent.writeln( - 'static var codec: FlutterStandardMessageCodec { ${_getCodecName(generatorOptions)}.shared }'); + 'static var codec: FlutterStandardMessageCodec { ${_getMessageCodecName(generatorOptions)}.shared }'); indent.writeln( '$_docCommentPrefix Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`.'); indent.write( @@ -764,7 +783,7 @@ if (wrapped == nil) { _docCommentSpec, ); indent.writeln( - 'var codec: FlutterStandardMessageCodec { ${_getCodecName(generatorOptions)}.shared }', + 'var codec: FlutterStandardMessageCodec { ${_getMessageCodecName(generatorOptions)}.shared }', ); indent.newln(); @@ -797,7 +816,7 @@ if (wrapped == nil) { '}', () { indent.writeln( - 'let codec = ${_getCodecName(generatorOptions)}.shared', + 'let codec = ${_getMessageCodecName(generatorOptions)}.shared', ); const String setHandlerCondition = 'let instanceManager = instanceManager'; @@ -899,7 +918,7 @@ if (wrapped == nil) { indent.newln(); indent.writeScoped( - 'private class $filePrefix${classNamePrefix}ProxyApiCodecReader: ${_getCodecName(generatorOptions)}Reader {', + 'private class $filePrefix${classNamePrefix}ProxyApiCodecReader: ${_getMessageCodecName(generatorOptions)}Reader {', '}', () { indent.writeln('unowned let pigeonRegistrar: $registrarName'); @@ -938,7 +957,7 @@ if (wrapped == nil) { indent.newln(); indent.writeScoped( - 'private class $filePrefix${classNamePrefix}ProxyApiCodecWriter: ${_getCodecName(generatorOptions)}Writer {', + 'private class $filePrefix${classNamePrefix}ProxyApiCodecWriter: ${_getMessageCodecName(generatorOptions)}Writer {', '}', () { indent.writeln( @@ -1304,23 +1323,15 @@ private func nilOrValue(_ value: Any?) -> T? { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasFlutterApi = root.apis - .whereType() - .any((Api api) => api.methods.isNotEmpty); - final bool hasProxyApi = root.apis.any((Api api) => api is AstProxyApi); - if (generatorOptions.includeErrorClass) { _writePigeonError(generatorOptions, indent); } - if (hasHostApi || hasProxyApi) { + if (root.containsHostApi || root.containsProxyApi) { _writeWrapResult(indent); _writeWrapError(generatorOptions, indent); } - if (hasFlutterApi || hasProxyApi) { + if (root.containsFlutterApi || root.containsProxyApi) { _writeCreateConnectionError(generatorOptions, indent); } @@ -1328,6 +1339,86 @@ private func nilOrValue(_ value: Any?) -> T? { _writeNilOrValue(indent); } + @override + void writeEventChannelApi( + SwiftOptions generatorOptions, + Root root, + Indent indent, + AstEventChannelApi api, { + required String dartPackageName, + }) { + indent.newln(); + indent.format(''' + private class PigeonStreamHandler: NSObject, FlutterStreamHandler { + private let wrapper: PigeonEventChannelWrapper + private var pigeonSink: PigeonEventSink? = nil + + init(wrapper: PigeonEventChannelWrapper) { + self.wrapper = wrapper + } + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + pigeonSink = PigeonEventSink(events) + wrapper.onListen(withArguments: arguments, sink: pigeonSink!) + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + pigeonSink = nil + wrapper.onCancel(withArguments: arguments) + return nil + } + } + + class PigeonEventChannelWrapper { + func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {} + func onCancel(withArguments arguments: Any?) {} + } + + class PigeonEventSink { + private let sink: FlutterEventSink + + init(_ sink: @escaping FlutterEventSink) { + self.sink = sink + } + + func success(_ value: ReturnType) { + sink(value) + } + + func error(code: String, message: String?, details: Any?) { + sink(FlutterError(code: code, message: message, details: details)) + } + + func endOfStream() { + sink(FlutterEndOfEventStream) + } + + } + '''); + addDocumentationComments( + indent, api.documentationComments, _docCommentSpec); + for (final Method func in api.methods) { + indent.format(''' + 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)" + } + let internalStreamHandler = PigeonStreamHandler<${_swiftTypeForDartType(func.returnType)}>(streamHandler: wrapper) + let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: ${_getMethodCodecVarName(generatorOptions)}) + channel.setStreamHandler(internalStreamHandler) + } + } + '''); + } + } + void _writeFlutterMethod( Indent indent, { required SwiftOptions generatorOptions, @@ -2491,8 +2582,13 @@ String? _tryGetUnsupportedPlatformsCondition(Iterable types) { } /// Calculates the name of the codec that will be generated for [api]. -String _getCodecName(SwiftOptions options) { - return '${options.fileSpecificClassNameComponent}PigeonCodec'; +String _getMessageCodecName(SwiftOptions options) { + return '${options.fileSpecificClassNameComponent ?? ''}PigeonCodec'; +} + +/// Calculates the name of the codec that will be generated for [api]. +String _getMethodCodecVarName(SwiftOptions options) { + return '${toLowerCamelCase(options.fileSpecificClassNameComponent ?? '')}PigeonMethodCodec'; } String _getErrorClassName(SwiftOptions generatorOptions) { diff --git a/packages/pigeon/pigeons/event_channel_tests.dart b/packages/pigeon/pigeons/event_channel_tests.dart new file mode 100644 index 000000000000..97602221d058 --- /dev/null +++ b/packages/pigeon/pigeons/event_channel_tests.dart @@ -0,0 +1,144 @@ +// 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'; + +enum EventEnum { + one, + two, + three, + fortyTwo, + fourHundredTwentyTwo, +} + +// Enums require special logic, having multiple ensures that the logic can be +// replicated without collision. +enum AnotherEventEnum { + justInCase, +} + +/// A class containing all supported nullable types. +@SwiftClass() +class EventAllNullableTypes { + EventAllNullableTypes( + this.aNullableBool, + this.aNullableInt, + this.aNullableInt64, + this.aNullableDouble, + this.aNullableByteArray, + this.aNullable4ByteArray, + this.aNullable8ByteArray, + this.aNullableFloatArray, + this.aNullableEnum, + this.anotherNullableEnum, + this.aNullableString, + this.aNullableObject, + this.allNullableTypes, + + // Lists + // This name is in a different format than the others to ensure that name + // collision with the word 'list' doesn't occur in the generated files. + this.list, + this.stringList, + this.intList, + this.doubleList, + this.boolList, + this.enumList, + this.objectList, + this.listList, + this.mapList, + this.recursiveClassList, + + // Maps + this.map, + this.stringMap, + this.intMap, + this.enumMap, + this.objectMap, + this.listMap, + this.mapMap, + this.recursiveClassMap, + ); + + bool? aNullableBool; + int? aNullableInt; + int? aNullableInt64; + double? aNullableDouble; + Uint8List? aNullableByteArray; + Int32List? aNullable4ByteArray; + Int64List? aNullable8ByteArray; + Float64List? aNullableFloatArray; + EventEnum? aNullableEnum; + AnotherEventEnum? anotherNullableEnum; + String? aNullableString; + Object? aNullableObject; + EventAllNullableTypes? allNullableTypes; + + // Lists + // ignore: strict_raw_type, always_specify_types + List? list; + List? stringList; + List? intList; + List? doubleList; + List? boolList; + List? enumList; + List? objectList; + List?>? listList; + List?>? mapList; + List? recursiveClassList; + + // Maps + // ignore: strict_raw_type, always_specify_types + Map? map; + Map? stringMap; + Map? intMap; + Map? enumMap; + Map? objectMap; + Map?>? listMap; + Map?>? mapMap; + Map? recursiveClassMap; +} + +sealed class PlatformEvent {} + +class IntEvent extends PlatformEvent { + IntEvent(this.value); + final int value; +} + +class StringEvent extends PlatformEvent { + StringEvent(this.value); + final String value; +} + +class BoolEvent extends PlatformEvent { + BoolEvent(this.value); + final bool value; +} + +class DoubleEvent extends PlatformEvent { + DoubleEvent(this.value); + final double value; +} + +class ObjectsEvent extends PlatformEvent { + ObjectsEvent(this.value); + final Object value; +} + +class EnumEvent extends PlatformEvent { + EnumEvent(this.value); + final EventEnum value; +} + +class ClassEvent extends PlatformEvent { + ClassEvent(this.value); + final EventAllNullableTypes value; +} + +@EventChannelApi() +abstract class EventChannelCoreApi { + int streamInts(); + PlatformEvent streamEvents(); +} diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/example_app.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/example_app.dart index 31075ae532f6..ddc3fdcd0296 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/example_app.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/example_app.dart @@ -26,6 +26,7 @@ class ExampleApp extends StatefulWidget { class _ExampleAppState extends State { late final HostIntegrationCoreApi api; String status = 'Calling...'; + String allData = ''; @override void initState() { @@ -58,7 +59,54 @@ class _ExampleAppState extends State { title: const Text('Pigeon integration tests'), ), body: Center( - child: Text(status), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Counter :', + ), + StreamBuilder( + stream: streamEvents(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + final PlatformEvent? data = snapshot.data; + if (snapshot.hasData) { + String newString = ''; + switch (data) { + case null: + newString = 'null, '; + case IntEvent(): + final IntEvent intData = data; + newString = '${intData.value}, '; + case StringEvent(): + final StringEvent stringData = data; + newString = '${stringData.value}, '; + case BoolEvent(): + final BoolEvent boolData = data; + newString = '${boolData.value}, '; + case DoubleEvent(): + final DoubleEvent doubleData = data; + newString = '${doubleData.value}, '; + case ObjectsEvent(): + final ObjectsEvent objectsData = data; + newString = '${objectsData.value}, '; + case EnumEvent(): + final EnumEvent enumData = data; + newString = '${enumData.value}, '; + case ClassEvent(): + final ClassEvent classData = data; + newString = '${classData.value}, '; + } + + allData += newString; + return Text(allData); + } else { + return const CircularProgressIndicator(); + } + }, + ), + ], + ), ), ), ); diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/generated.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/generated.dart index b7861ee4d574..76d63eb0fd86 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/generated.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/generated.dart @@ -3,5 +3,6 @@ // found in the LICENSE file. export 'src/generated/core_tests.gen.dart'; +export 'src/generated/event_channel_tests.gen.dart'; export 'src/generated/proxy_api_tests.gen.dart' show ProxyApiSuperClass, ProxyApiTestClass, ProxyApiTestEnum; diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart index 21d76637675c..17faa0421ae2 100644 --- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart @@ -4,6 +4,8 @@ // ignore_for_file: unused_local_variable +import 'dart:async'; + import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -2840,6 +2842,60 @@ void runPigeonIntegrationTests(TargetGenerator targetGenerator) { final UnusedClass unused = UnusedClass(); expect(unused, unused); }); + + /// Event channels + + const List eventChannelSupported = [ + TargetGenerator.kotlin, + TargetGenerator.swift + ]; + testWidgets('event channel sends continuous ints', (_) async { + final Stream events = streamInts(); + final List listEvents = await events.toList(); + for (final int value in listEvents) { + expect(listEvents[value], value); + } + }, skip: !eventChannelSupported.contains(targetGenerator)); + + testWidgets('event channel handles extended sealed classes', (_) async { + final Completer completer = Completer(); + int count = 0; + final Stream events = streamEvents(); + events.listen((PlatformEvent event) { + switch (event) { + case IntEvent(): + expect(event.value, 1); + expect(count, 0); + count++; + case StringEvent(): + expect(event.value, 'string'); + expect(count, 1); + count++; + case BoolEvent(): + expect(event.value, false); + expect(count, 2); + count++; + case DoubleEvent(): + expect(event.value, 3.14); + expect(count, 3); + count++; + case ObjectsEvent(): + expect(event.value, true); + expect(count, 4); + count++; + case EnumEvent(): + expect(event.value, EventEnum.fortyTwo); + expect(count, 5); + count++; + case ClassEvent(): + expect(event.value.aNullableInt, 0); + expect(count, 6); + count++; + completer.complete(); + } + }); + await completer.future; + }, skip: !eventChannelSupported.contains(targetGenerator)); } class _FlutterApiTestImplementation implements FlutterIntegrationCoreApi { 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 new file mode 100644 index 000000000000..368a3408d5f4 --- /dev/null +++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/event_channel_tests.gen.dart @@ -0,0 +1,453 @@ +// 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'; + +enum EventEnum { + one, + two, + three, + fortyTwo, + fourHundredTwentyTwo, +} + +enum AnotherEventEnum { + justInCase, +} + +/// A class containing all supported nullable types. +class EventAllNullableTypes { + EventAllNullableTypes({ + this.aNullableBool, + this.aNullableInt, + this.aNullableInt64, + this.aNullableDouble, + this.aNullableByteArray, + this.aNullable4ByteArray, + this.aNullable8ByteArray, + this.aNullableFloatArray, + this.aNullableEnum, + this.anotherNullableEnum, + this.aNullableString, + this.aNullableObject, + this.allNullableTypes, + this.list, + this.stringList, + this.intList, + this.doubleList, + this.boolList, + this.enumList, + this.objectList, + this.listList, + this.mapList, + this.recursiveClassList, + this.map, + this.stringMap, + this.intMap, + this.enumMap, + this.objectMap, + this.listMap, + this.mapMap, + this.recursiveClassMap, + }); + + bool? aNullableBool; + + int? aNullableInt; + + int? aNullableInt64; + + double? aNullableDouble; + + Uint8List? aNullableByteArray; + + Int32List? aNullable4ByteArray; + + Int64List? aNullable8ByteArray; + + Float64List? aNullableFloatArray; + + EventEnum? aNullableEnum; + + AnotherEventEnum? anotherNullableEnum; + + String? aNullableString; + + Object? aNullableObject; + + EventAllNullableTypes? allNullableTypes; + + List? list; + + List? stringList; + + List? intList; + + List? doubleList; + + List? boolList; + + List? enumList; + + List? objectList; + + List?>? listList; + + List?>? mapList; + + List? recursiveClassList; + + Map? map; + + Map? stringMap; + + Map? intMap; + + Map? enumMap; + + Map? objectMap; + + Map?>? listMap; + + Map?>? mapMap; + + Map? recursiveClassMap; + + Object encode() { + return [ + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableEnum, + anotherNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + enumList, + objectList, + listList, + mapList, + recursiveClassList, + map, + stringMap, + intMap, + enumMap, + objectMap, + listMap, + mapMap, + recursiveClassMap, + ]; + } + + static EventAllNullableTypes decode(Object result) { + result as List; + return EventAllNullableTypes( + aNullableBool: result[0] as bool?, + aNullableInt: result[1] as int?, + aNullableInt64: result[2] as int?, + aNullableDouble: result[3] as double?, + aNullableByteArray: result[4] as Uint8List?, + aNullable4ByteArray: result[5] as Int32List?, + aNullable8ByteArray: result[6] as Int64List?, + aNullableFloatArray: result[7] as Float64List?, + aNullableEnum: result[8] as EventEnum?, + anotherNullableEnum: result[9] as AnotherEventEnum?, + aNullableString: result[10] as String?, + aNullableObject: result[11], + allNullableTypes: result[12] as EventAllNullableTypes?, + list: result[13] as List?, + stringList: (result[14] as List?)?.cast(), + intList: (result[15] as List?)?.cast(), + doubleList: (result[16] as List?)?.cast(), + boolList: (result[17] as List?)?.cast(), + enumList: (result[18] as List?)?.cast(), + objectList: (result[19] as List?)?.cast(), + listList: (result[20] as List?)?.cast?>(), + mapList: (result[21] as List?)?.cast?>(), + recursiveClassList: + (result[22] as List?)?.cast(), + map: result[23] as Map?, + stringMap: + (result[24] as Map?)?.cast(), + intMap: (result[25] as Map?)?.cast(), + enumMap: (result[26] as Map?) + ?.cast(), + objectMap: + (result[27] as Map?)?.cast(), + listMap: + (result[28] as Map?)?.cast?>(), + mapMap: (result[29] as Map?) + ?.cast?>(), + recursiveClassMap: (result[30] as Map?) + ?.cast(), + ); + } +} + +sealed class PlatformEvent {} + +class IntEvent extends PlatformEvent { + IntEvent({ + required this.value, + }); + + int value; + + Object encode() { + return [ + value, + ]; + } + + static IntEvent decode(Object result) { + result as List; + return IntEvent( + value: result[0]! as int, + ); + } +} + +class StringEvent extends PlatformEvent { + StringEvent({ + required this.value, + }); + + String value; + + Object encode() { + return [ + value, + ]; + } + + static StringEvent decode(Object result) { + result as List; + return StringEvent( + value: result[0]! as String, + ); + } +} + +class BoolEvent extends PlatformEvent { + BoolEvent({ + required this.value, + }); + + bool value; + + Object encode() { + return [ + value, + ]; + } + + static BoolEvent decode(Object result) { + result as List; + return BoolEvent( + value: result[0]! as bool, + ); + } +} + +class DoubleEvent extends PlatformEvent { + DoubleEvent({ + required this.value, + }); + + double value; + + Object encode() { + return [ + value, + ]; + } + + static DoubleEvent decode(Object result) { + result as List; + return DoubleEvent( + value: result[0]! as double, + ); + } +} + +class ObjectsEvent extends PlatformEvent { + ObjectsEvent({ + required this.value, + }); + + Object value; + + Object encode() { + return [ + value, + ]; + } + + static ObjectsEvent decode(Object result) { + result as List; + return ObjectsEvent( + value: result[0]!, + ); + } +} + +class EnumEvent extends PlatformEvent { + EnumEvent({ + required this.value, + }); + + EventEnum value; + + Object encode() { + return [ + value, + ]; + } + + static EnumEvent decode(Object result) { + result as List; + return EnumEvent( + value: result[0]! as EventEnum, + ); + } +} + +class ClassEvent extends PlatformEvent { + ClassEvent({ + required this.value, + }); + + EventAllNullableTypes value; + + Object encode() { + return [ + value, + ]; + } + + static ClassEvent decode(Object result) { + result as List; + return ClassEvent( + value: result[0]! as EventAllNullableTypes, + ); + } +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is EventEnum) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is AnotherEventEnum) { + buffer.putUint8(130); + writeValue(buffer, value.index); + } else if (value is EventAllNullableTypes) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is IntEvent) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else if (value is StringEvent) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is BoolEvent) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is DoubleEvent) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is ObjectsEvent) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is EnumEvent) { + buffer.putUint8(137); + writeValue(buffer, value.encode()); + } else if (value is ClassEvent) { + buffer.putUint8(138); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : EventEnum.values[value]; + case 130: + final int? value = readValue(buffer) as int?; + return value == null ? null : AnotherEventEnum.values[value]; + case 131: + return EventAllNullableTypes.decode(readValue(buffer)!); + case 132: + return IntEvent.decode(readValue(buffer)!); + case 133: + return StringEvent.decode(readValue(buffer)!); + case 134: + return BoolEvent.decode(readValue(buffer)!); + case 135: + return DoubleEvent.decode(readValue(buffer)!); + case 136: + return ObjectsEvent.decode(readValue(buffer)!); + case 137: + return EnumEvent.decode(readValue(buffer)!); + case 138: + return ClassEvent.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +const StandardMethodCodec pigeonMethodCodec = + StandardMethodCodec(_PigeonCodec()); + +Stream streamInts({String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.$instanceName'; + } + const EventChannel streamIntsChannel = EventChannel( + 'dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamInts', + pigeonMethodCodec); + return streamIntsChannel.receiveBroadcastStream().map((dynamic event) { + return event as int; + }); +} + +Stream streamEvents({String instanceName = ''}) { + if (instanceName.isNotEmpty) { + instanceName = '.$instanceName'; + } + const EventChannel streamEventsChannel = EventChannel( + 'dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamEvents', + pigeonMethodCodec); + return streamEventsChannel.receiveBroadcastStream().map((dynamic event) { + return event as PlatformEvent; + }); +} diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/.gitignore b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/.gitignore index 5f3fcaf31604..bbe6aef544f5 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/.gitignore +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/.gitignore @@ -7,5 +7,7 @@ # This contains the declaration of the test classes wrapped by the ProxyApi tests and the # implemetations of their APIs. !ProxyApiTestApiImpls.kt -# Including this makes it easier to review code generation changes. + +# Including these makes it easier to review code generation changes. !ProxyApiTests.gen.kt +!EventChannelTests.gen.kt 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 new file mode 100644 index 000000000000..4f7c7efadb6c --- /dev/null +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/EventChannelTests.gen.kt @@ -0,0 +1,472 @@ +// 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 +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package com.example.test_plugin + +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class EventChannelTestsError( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +enum class EventEnum(val raw: Int) { + ONE(0), + TWO(1), + THREE(2), + FORTY_TWO(3), + FOUR_HUNDRED_TWENTY_TWO(4); + + companion object { + fun ofRaw(raw: Int): EventEnum? { + return values().firstOrNull { it.raw == raw } + } + } +} + +enum class AnotherEventEnum(val raw: Int) { + JUST_IN_CASE(0); + + companion object { + fun ofRaw(raw: Int): AnotherEventEnum? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * A class containing all supported nullable types. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class EventAllNullableTypes( + val aNullableBool: Boolean? = null, + val aNullableInt: Long? = null, + val aNullableInt64: Long? = null, + val aNullableDouble: Double? = null, + val aNullableByteArray: ByteArray? = null, + val aNullable4ByteArray: IntArray? = null, + val aNullable8ByteArray: LongArray? = null, + val aNullableFloatArray: DoubleArray? = null, + val aNullableEnum: EventEnum? = null, + val anotherNullableEnum: AnotherEventEnum? = null, + val aNullableString: String? = null, + val aNullableObject: Any? = null, + val allNullableTypes: EventAllNullableTypes? = null, + val list: List? = null, + val stringList: List? = null, + val intList: List? = null, + val doubleList: List? = null, + val boolList: List? = null, + val enumList: List? = null, + val objectList: List? = null, + val listList: List?>? = null, + val mapList: List?>? = null, + val recursiveClassList: List? = null, + val map: Map? = null, + val stringMap: Map? = null, + val intMap: Map? = null, + val enumMap: Map? = null, + val objectMap: Map? = null, + val listMap: Map?>? = null, + val mapMap: Map?>? = null, + val recursiveClassMap: Map? = null +) { + companion object { + fun fromList(pigeonVar_list: List): EventAllNullableTypes { + val aNullableBool = pigeonVar_list[0] as Boolean? + val aNullableInt = pigeonVar_list[1] as Long? + val aNullableInt64 = pigeonVar_list[2] as Long? + val aNullableDouble = pigeonVar_list[3] as Double? + val aNullableByteArray = pigeonVar_list[4] as ByteArray? + val aNullable4ByteArray = pigeonVar_list[5] as IntArray? + val aNullable8ByteArray = pigeonVar_list[6] as LongArray? + val aNullableFloatArray = pigeonVar_list[7] as DoubleArray? + val aNullableEnum = pigeonVar_list[8] as EventEnum? + val anotherNullableEnum = pigeonVar_list[9] as AnotherEventEnum? + val aNullableString = pigeonVar_list[10] as String? + val aNullableObject = pigeonVar_list[11] + val allNullableTypes = pigeonVar_list[12] as EventAllNullableTypes? + val list = pigeonVar_list[13] as List? + val stringList = pigeonVar_list[14] as List? + val intList = pigeonVar_list[15] as List? + val doubleList = pigeonVar_list[16] as List? + val boolList = pigeonVar_list[17] as List? + val enumList = pigeonVar_list[18] as List? + val objectList = pigeonVar_list[19] as List? + val listList = pigeonVar_list[20] as List?>? + val mapList = pigeonVar_list[21] as List?>? + val recursiveClassList = pigeonVar_list[22] as List? + val map = pigeonVar_list[23] as Map? + val stringMap = pigeonVar_list[24] as Map? + val intMap = pigeonVar_list[25] as Map? + val enumMap = pigeonVar_list[26] as Map? + val objectMap = pigeonVar_list[27] as Map? + val listMap = pigeonVar_list[28] as Map?>? + val mapMap = pigeonVar_list[29] as Map?>? + val recursiveClassMap = pigeonVar_list[30] as Map? + return EventAllNullableTypes( + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableEnum, + anotherNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + enumList, + objectList, + listList, + mapList, + recursiveClassList, + map, + stringMap, + intMap, + enumMap, + objectMap, + listMap, + mapMap, + recursiveClassMap) + } + } + + fun toList(): List { + return listOf( + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableEnum, + anotherNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + enumList, + objectList, + listList, + mapList, + recursiveClassList, + map, + stringMap, + intMap, + enumMap, + objectMap, + listMap, + mapMap, + recursiveClassMap, + ) + } +} + +/** + * Generated class from Pigeon that represents data sent in messages. This class should not be + * extended by any user class outside of the generated file. + */ +sealed class PlatformEvent +/** Generated class from Pigeon that represents data sent in messages. */ +data class IntEvent(val value: Long) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): IntEvent { + val value = pigeonVar_list[0] as Long + return IntEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class StringEvent(val value: String) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): StringEvent { + val value = pigeonVar_list[0] as String + return StringEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class BoolEvent(val value: Boolean) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): BoolEvent { + val value = pigeonVar_list[0] as Boolean + return BoolEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class DoubleEvent(val value: Double) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): DoubleEvent { + val value = pigeonVar_list[0] as Double + return DoubleEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class ObjectsEvent(val value: Any) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): ObjectsEvent { + val value = pigeonVar_list[0] as Any + return ObjectsEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class EnumEvent(val value: EventEnum) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): EnumEvent { + val value = pigeonVar_list[0] as EventEnum + return EnumEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class ClassEvent(val value: EventAllNullableTypes) : PlatformEvent() { + companion object { + fun fromList(pigeonVar_list: List): ClassEvent { + val value = pigeonVar_list[0] as EventAllNullableTypes + return ClassEvent(value) + } + } + + fun toList(): List { + return listOf( + value, + ) + } +} + +private open class EventChannelTestsPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { EventEnum.ofRaw(it.toInt()) } + } + 130.toByte() -> { + return (readValue(buffer) as Long?)?.let { AnotherEventEnum.ofRaw(it.toInt()) } + } + 131.toByte() -> { + return (readValue(buffer) as? List)?.let { EventAllNullableTypes.fromList(it) } + } + 132.toByte() -> { + return (readValue(buffer) as? List)?.let { IntEvent.fromList(it) } + } + 133.toByte() -> { + return (readValue(buffer) as? List)?.let { StringEvent.fromList(it) } + } + 134.toByte() -> { + return (readValue(buffer) as? List)?.let { BoolEvent.fromList(it) } + } + 135.toByte() -> { + return (readValue(buffer) as? List)?.let { DoubleEvent.fromList(it) } + } + 136.toByte() -> { + return (readValue(buffer) as? List)?.let { ObjectsEvent.fromList(it) } + } + 137.toByte() -> { + return (readValue(buffer) as? List)?.let { EnumEvent.fromList(it) } + } + 138.toByte() -> { + return (readValue(buffer) as? List)?.let { ClassEvent.fromList(it) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is EventEnum -> { + stream.write(129) + writeValue(stream, value.raw) + } + is AnotherEventEnum -> { + stream.write(130) + writeValue(stream, value.raw) + } + is EventAllNullableTypes -> { + stream.write(131) + writeValue(stream, value.toList()) + } + is IntEvent -> { + stream.write(132) + writeValue(stream, value.toList()) + } + is StringEvent -> { + stream.write(133) + writeValue(stream, value.toList()) + } + is BoolEvent -> { + stream.write(134) + writeValue(stream, value.toList()) + } + is DoubleEvent -> { + stream.write(135) + writeValue(stream, value.toList()) + } + is ObjectsEvent -> { + stream.write(136) + writeValue(stream, value.toList()) + } + is EnumEvent -> { + stream.write(137) + writeValue(stream, value.toList()) + } + is ClassEvent -> { + stream.write(138) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +val EventChannelTestsPigeonMethodCodec = StandardMethodCodec(EventChannelTestsPigeonCodec()) + +private class PigeonStreamHandler(val wrapper: PigeonEventChannelWrapper) : + EventChannel.StreamHandler { + var pigeonSink: PigeonEventSink? = null + + override fun onListen(p0: Any?, sink: EventChannel.EventSink) { + pigeonSink = PigeonEventSink(sink) + wrapper.onListen(p0, pigeonSink!!) + } + + override fun onCancel(p0: Any?) { + pigeonSink = null + wrapper.onCancel(p0) + } +} + +interface PigeonEventChannelWrapper { + open fun onListen(p0: Any?, sink: PigeonEventSink) {} + + open fun onCancel(p0: Any?) {} +} + +class PigeonEventSink(private val sink: EventChannel.EventSink) { + fun success(value: T) { + sink.success(value) + } + + fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + sink.error(errorCode, errorMessage, errorDetails) + } + + fun endOfStream() { + sink.endOfStream() + } +} + +abstract class StreamIntsStreamHandler : PigeonEventChannelWrapper { + companion object { + fun register( + messenger: BinaryMessenger, + streamHandler: StreamIntsStreamHandler, + instanceName: String = "" + ) { + var channelName: String = + "dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamInts" + if (instanceName.isNotEmpty()) { + channelName += ".$instanceName" + } + val internalStreamHandler = PigeonStreamHandler(streamHandler) + EventChannel(messenger, channelName, EventChannelTestsPigeonMethodCodec) + .setStreamHandler(internalStreamHandler) + } + } +} + +abstract class StreamEventsStreamHandler : PigeonEventChannelWrapper { + companion object { + fun register( + messenger: BinaryMessenger, + streamHandler: StreamEventsStreamHandler, + instanceName: String = "" + ) { + var channelName: String = + "dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamEvents" + if (instanceName.isNotEmpty()) { + channelName += ".$instanceName" + } + val internalStreamHandler = PigeonStreamHandler(streamHandler) + EventChannel(messenger, channelName, EventChannelTestsPigeonMethodCodec) + .setStreamHandler(internalStreamHandler) + } + } +} diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt index 9563941624d7..f175076abcda 100644 --- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt +++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt @@ -4,6 +4,8 @@ package com.example.test_plugin +import android.os.Handler +import android.os.Looper import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding @@ -26,6 +28,9 @@ class TestPlugin : FlutterPlugin, HostIntegrationCoreApi { proxyApiRegistrar = ProxyApiRegistrar(binding.binaryMessenger) proxyApiRegistrar!!.setUp() + + StreamEventsStreamHandler.register(binding.binaryMessenger, SendClass) + StreamIntsStreamHandler.register(binding.binaryMessenger, SendInts) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { @@ -867,3 +872,58 @@ class TestPluginWithSuffix : HostSmallApi { callback(Result.success(Unit)) } } + +object SendInts : StreamIntsStreamHandler() { + val handler = Handler(Looper.getMainLooper()) + + override fun onListen(p0: Any?, sink: PigeonEventSink) { + var count: Long = 0 + val r: Runnable = + object : Runnable { + override fun run() { + handler.post { + if (count >= 5) { + sink.endOfStream() + } else { + sink.success(count) + count++ + handler.postDelayed(this, 10) + } + } + } + } + handler.postDelayed(r, 10) + } +} + +object SendClass : StreamEventsStreamHandler() { + val handler = Handler(Looper.getMainLooper()) + val eventList = + listOf( + IntEvent(1), + StringEvent("string"), + BoolEvent(false), + DoubleEvent(3.14), + ObjectsEvent(true), + EnumEvent(EventEnum.FORTY_TWO), + ClassEvent(EventAllNullableTypes(aNullableInt = 0))) + + override fun onListen(p0: Any?, sink: PigeonEventSink) { + var count: Int = 0 + val r: Runnable = + object : Runnable { + override fun run() { + if (count >= eventList.size) { + sink.endOfStream() + } else { + handler.post { + sink.success(eventList[count]) + count++ + } + handler.postDelayed(this, 10) + } + } + } + handler.postDelayed(r, 10) + } +} diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/AppDelegate.swift b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/AppDelegate.swift index 5cec4c48f620..21fbd0297b22 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/AppDelegate.swift +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/AppDelegate.swift @@ -5,9 +5,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/.gitignore b/packages/pigeon/platform_tests/test_plugin/ios/Classes/.gitignore index 18ad850358d9..94e339ef912d 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/.gitignore +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/.gitignore @@ -5,5 +5,7 @@ !CoreTests.gen.swift # Keeping this makes it easier to review changes to ProxyApi generation. !ProxyApiTests.gen.swift -# Contains the class declartions for testing ProxyApis. + +# Including these makes it easier to review code generation changes. !ProxyApiTestClass.swift +!EventChannelTests.gen.swift diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift index 2b2001d6aa44..a90e8fa07a5d 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift @@ -15,24 +15,6 @@ import Foundation #error("Unsupported platform.") #endif -/// Error class for passing custom error details to Dart side. -final class PigeonError: Error { - let code: String - let message: String? - let details: Any? - - init(code: String, message: String?, details: Any?) { - self.code = code - self.message = message - self.details = details - } - - var localizedDescription: String { - return - "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } -} - private func wrapResult(_ result: Any?) -> [Any?] { return [result] } diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/EventChannelTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/EventChannelTests.gen.swift new file mode 100644 index 000000000000..581253256bc4 --- /dev/null +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/EventChannelTests.gen.swift @@ -0,0 +1,576 @@ +// 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 + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class EventChannelTestsError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "EventChannelTestsError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +enum EventEnum: Int { + case one = 0 + case two = 1 + case three = 2 + case fortyTwo = 3 + case fourHundredTwentyTwo = 4 +} + +enum AnotherEventEnum: Int { + case justInCase = 0 +} + +/// A class containing all supported nullable types. +/// +/// Generated class from Pigeon that represents data sent in messages. +class EventAllNullableTypes { + init( + aNullableBool: Bool? = nil, + aNullableInt: Int64? = nil, + aNullableInt64: Int64? = nil, + aNullableDouble: Double? = nil, + aNullableByteArray: FlutterStandardTypedData? = nil, + aNullable4ByteArray: FlutterStandardTypedData? = nil, + aNullable8ByteArray: FlutterStandardTypedData? = nil, + aNullableFloatArray: FlutterStandardTypedData? = nil, + aNullableEnum: EventEnum? = nil, + anotherNullableEnum: AnotherEventEnum? = nil, + aNullableString: String? = nil, + aNullableObject: Any? = nil, + allNullableTypes: EventAllNullableTypes? = nil, + list: [Any?]? = nil, + stringList: [String?]? = nil, + intList: [Int64?]? = nil, + doubleList: [Double?]? = nil, + boolList: [Bool?]? = nil, + enumList: [EventEnum?]? = nil, + objectList: [Any?]? = nil, + listList: [[Any?]?]? = nil, + mapList: [[AnyHashable?: Any?]?]? = nil, + recursiveClassList: [EventAllNullableTypes?]? = nil, + map: [AnyHashable?: Any?]? = nil, + stringMap: [String?: String?]? = nil, + intMap: [Int64?: Int64?]? = nil, + enumMap: [EventEnum?: EventEnum?]? = nil, + objectMap: [AnyHashable?: Any?]? = nil, + listMap: [Int64?: [Any?]?]? = nil, + mapMap: [Int64?: [AnyHashable?: Any?]?]? = nil, + recursiveClassMap: [Int64?: EventAllNullableTypes?]? = nil + ) { + self.aNullableBool = aNullableBool + self.aNullableInt = aNullableInt + self.aNullableInt64 = aNullableInt64 + self.aNullableDouble = aNullableDouble + self.aNullableByteArray = aNullableByteArray + self.aNullable4ByteArray = aNullable4ByteArray + self.aNullable8ByteArray = aNullable8ByteArray + self.aNullableFloatArray = aNullableFloatArray + self.aNullableEnum = aNullableEnum + self.anotherNullableEnum = anotherNullableEnum + self.aNullableString = aNullableString + self.aNullableObject = aNullableObject + self.allNullableTypes = allNullableTypes + self.list = list + self.stringList = stringList + self.intList = intList + self.doubleList = doubleList + self.boolList = boolList + self.enumList = enumList + self.objectList = objectList + self.listList = listList + self.mapList = mapList + self.recursiveClassList = recursiveClassList + self.map = map + self.stringMap = stringMap + self.intMap = intMap + self.enumMap = enumMap + self.objectMap = objectMap + self.listMap = listMap + self.mapMap = mapMap + self.recursiveClassMap = recursiveClassMap + } + var aNullableBool: Bool? + var aNullableInt: Int64? + var aNullableInt64: Int64? + var aNullableDouble: Double? + var aNullableByteArray: FlutterStandardTypedData? + var aNullable4ByteArray: FlutterStandardTypedData? + var aNullable8ByteArray: FlutterStandardTypedData? + var aNullableFloatArray: FlutterStandardTypedData? + var aNullableEnum: EventEnum? + var anotherNullableEnum: AnotherEventEnum? + var aNullableString: String? + var aNullableObject: Any? + var allNullableTypes: EventAllNullableTypes? + var list: [Any?]? + var stringList: [String?]? + var intList: [Int64?]? + var doubleList: [Double?]? + var boolList: [Bool?]? + var enumList: [EventEnum?]? + var objectList: [Any?]? + var listList: [[Any?]?]? + var mapList: [[AnyHashable?: Any?]?]? + var recursiveClassList: [EventAllNullableTypes?]? + var map: [AnyHashable?: Any?]? + var stringMap: [String?: String?]? + var intMap: [Int64?: Int64?]? + var enumMap: [EventEnum?: EventEnum?]? + var objectMap: [AnyHashable?: Any?]? + var listMap: [Int64?: [Any?]?]? + var mapMap: [Int64?: [AnyHashable?: Any?]?]? + var recursiveClassMap: [Int64?: EventAllNullableTypes?]? + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> EventAllNullableTypes? { + let aNullableBool: Bool? = nilOrValue(pigeonVar_list[0]) + let aNullableInt: Int64? = nilOrValue(pigeonVar_list[1]) + let aNullableInt64: Int64? = nilOrValue(pigeonVar_list[2]) + let aNullableDouble: Double? = nilOrValue(pigeonVar_list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[7]) + let aNullableEnum: EventEnum? = nilOrValue(pigeonVar_list[8]) + let anotherNullableEnum: AnotherEventEnum? = nilOrValue(pigeonVar_list[9]) + let aNullableString: String? = nilOrValue(pigeonVar_list[10]) + let aNullableObject: Any? = pigeonVar_list[11] + let allNullableTypes: EventAllNullableTypes? = nilOrValue(pigeonVar_list[12]) + let list: [Any?]? = nilOrValue(pigeonVar_list[13]) + let stringList: [String?]? = nilOrValue(pigeonVar_list[14]) + let intList: [Int64?]? = nilOrValue(pigeonVar_list[15]) + let doubleList: [Double?]? = nilOrValue(pigeonVar_list[16]) + let boolList: [Bool?]? = nilOrValue(pigeonVar_list[17]) + let enumList: [EventEnum?]? = nilOrValue(pigeonVar_list[18]) + let objectList: [Any?]? = nilOrValue(pigeonVar_list[19]) + let listList: [[Any?]?]? = nilOrValue(pigeonVar_list[20]) + let mapList: [[AnyHashable?: Any?]?]? = nilOrValue(pigeonVar_list[21]) + let recursiveClassList: [EventAllNullableTypes?]? = nilOrValue(pigeonVar_list[22]) + let map: [AnyHashable?: Any?]? = nilOrValue(pigeonVar_list[23]) + let stringMap: [String?: String?]? = nilOrValue(pigeonVar_list[24]) + let intMap: [Int64?: Int64?]? = nilOrValue(pigeonVar_list[25]) + let enumMap: [EventEnum?: EventEnum?]? = pigeonVar_list[26] as? [EventEnum?: EventEnum?] + let objectMap: [AnyHashable?: Any?]? = nilOrValue(pigeonVar_list[27]) + let listMap: [Int64?: [Any?]?]? = nilOrValue(pigeonVar_list[28]) + let mapMap: [Int64?: [AnyHashable?: Any?]?]? = nilOrValue(pigeonVar_list[29]) + let recursiveClassMap: [Int64?: EventAllNullableTypes?]? = nilOrValue(pigeonVar_list[30]) + + return EventAllNullableTypes( + aNullableBool: aNullableBool, + aNullableInt: aNullableInt, + aNullableInt64: aNullableInt64, + aNullableDouble: aNullableDouble, + aNullableByteArray: aNullableByteArray, + aNullable4ByteArray: aNullable4ByteArray, + aNullable8ByteArray: aNullable8ByteArray, + aNullableFloatArray: aNullableFloatArray, + aNullableEnum: aNullableEnum, + anotherNullableEnum: anotherNullableEnum, + aNullableString: aNullableString, + aNullableObject: aNullableObject, + allNullableTypes: allNullableTypes, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + enumList: enumList, + objectList: objectList, + listList: listList, + mapList: mapList, + recursiveClassList: recursiveClassList, + map: map, + stringMap: stringMap, + intMap: intMap, + enumMap: enumMap, + objectMap: objectMap, + listMap: listMap, + mapMap: mapMap, + recursiveClassMap: recursiveClassMap + ) + } + func toList() -> [Any?] { + return [ + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableEnum, + anotherNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + enumList, + objectList, + listList, + mapList, + recursiveClassList, + map, + stringMap, + intMap, + enumMap, + objectMap, + listMap, + mapMap, + recursiveClassMap, + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +/// This class should not be extended by any user class outside of the generated file. +protocol PlatformEvent { + +} + +/// Generated class from Pigeon that represents data sent in messages. +struct IntEvent: PlatformEvent { + var value: Int64 + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> IntEvent? { + let value = pigeonVar_list[0] as! Int64 + + return IntEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct StringEvent: PlatformEvent { + var value: String + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> StringEvent? { + let value = pigeonVar_list[0] as! String + + return StringEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct BoolEvent: PlatformEvent { + var value: Bool + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> BoolEvent? { + let value = pigeonVar_list[0] as! Bool + + return BoolEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct DoubleEvent: PlatformEvent { + var value: Double + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> DoubleEvent? { + let value = pigeonVar_list[0] as! Double + + return DoubleEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct ObjectsEvent: PlatformEvent { + var value: Any + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> ObjectsEvent? { + let value = pigeonVar_list[0]! + + return ObjectsEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct EnumEvent: PlatformEvent { + var value: EventEnum + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> EnumEvent? { + let value = pigeonVar_list[0] as! EventEnum + + return EnumEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct ClassEvent: PlatformEvent { + var value: EventAllNullableTypes + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> ClassEvent? { + let value = pigeonVar_list[0] as! EventAllNullableTypes + + return ClassEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +private class EventChannelTestsPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return EventEnum(rawValue: enumResultAsInt) + } + return nil + case 130: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return AnotherEventEnum(rawValue: enumResultAsInt) + } + return nil + case 131: + return EventAllNullableTypes.fromList(self.readValue() as! [Any?]) + case 132: + return IntEvent.fromList(self.readValue() as! [Any?]) + case 133: + return StringEvent.fromList(self.readValue() as! [Any?]) + case 134: + return BoolEvent.fromList(self.readValue() as! [Any?]) + case 135: + return DoubleEvent.fromList(self.readValue() as! [Any?]) + case 136: + return ObjectsEvent.fromList(self.readValue() as! [Any?]) + case 137: + return EnumEvent.fromList(self.readValue() as! [Any?]) + case 138: + return ClassEvent.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class EventChannelTestsPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? EventEnum { + super.writeByte(129) + super.writeValue(value.rawValue) + } else if let value = value as? AnotherEventEnum { + super.writeByte(130) + super.writeValue(value.rawValue) + } else if let value = value as? EventAllNullableTypes { + super.writeByte(131) + super.writeValue(value.toList()) + } else if let value = value as? IntEvent { + super.writeByte(132) + super.writeValue(value.toList()) + } else if let value = value as? StringEvent { + super.writeByte(133) + super.writeValue(value.toList()) + } else if let value = value as? BoolEvent { + super.writeByte(134) + super.writeValue(value.toList()) + } else if let value = value as? DoubleEvent { + super.writeByte(135) + super.writeValue(value.toList()) + } else if let value = value as? ObjectsEvent { + super.writeByte(136) + super.writeValue(value.toList()) + } else if let value = value as? EnumEvent { + super.writeByte(137) + super.writeValue(value.toList()) + } else if let value = value as? ClassEvent { + super.writeByte(138) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class EventChannelTestsPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return EventChannelTestsPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return EventChannelTestsPigeonCodecWriter(data: data) + } +} + +class EventChannelTestsPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = EventChannelTestsPigeonCodec( + readerWriter: EventChannelTestsPigeonCodecReaderWriter()) +} + +var eventChannelTestsPigeonMethodCodec = FlutterStandardMethodCodec( + readerWriter: EventChannelTestsPigeonCodecReaderWriter()) + +private class PigeonStreamHandler: NSObject, FlutterStreamHandler { + private let wrapper: PigeonEventChannelWrapper + private var pigeonSink: PigeonEventSink? = nil + + init(wrapper: PigeonEventChannelWrapper) { + self.wrapper = wrapper + } + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + pigeonSink = PigeonEventSink(events) + wrapper.onListen(withArguments: arguments, sink: pigeonSink!) + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + pigeonSink = nil + wrapper.onCancel(withArguments: arguments) + return nil + } +} + +class PigeonEventChannelWrapper { + func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {} + func onCancel(withArguments arguments: Any?) {} +} + +class PigeonEventSink { + private let sink: FlutterEventSink + + init(_ sink: @escaping FlutterEventSink) { + self.sink = sink + } + + func success(_ value: ReturnType) { + sink(value) + } + + func error(code: String, message: String?, details: Any?) { + sink(FlutterError(code: code, message: message, details: details)) + } + + func endOfStream() { + sink(FlutterEndOfEventStream) + } + +} + +class StreamIntsStreamHandler: PigeonEventChannelWrapper { + static func register( + with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamIntsStreamHandler + ) { + var channelName = "dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamInts" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(streamHandler: wrapper) + let channel = FlutterEventChannel( + name: channelName, binaryMessenger: messenger, codec: eventChannelTestsPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} + +class StreamEventsStreamHandler: PigeonEventChannelWrapper { + static func register( + with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamEventsStreamHandler + ) { + var channelName = "dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamEvents" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(streamHandler: wrapper) + let channel = FlutterEventChannel( + name: channelName, binaryMessenger: messenger, codec: eventChannelTestsPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift index b4238de3be91..5c15ff7f77d8 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift @@ -28,6 +28,9 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { binaryMessenger: binaryMessenger, messageChannelSuffix: "suffixOne") flutterSmallApiTwo = FlutterSmallApi( binaryMessenger: binaryMessenger, messageChannelSuffix: "suffixTwo") + + StreamIntsStreamHandler.register(with: binaryMessenger, wrapper: SendInts()) + StreamEventsStreamHandler.register(with: binaryMessenger, wrapper: SendEvents()) proxyApiRegistrar = ProxyApiTestsPigeonProxyApiRegistrar( binaryMessenger: binaryMessenger, apiDelegate: ProxyApiDelegate()) proxyApiRegistrar!.setUp() @@ -1214,6 +1217,61 @@ public class TestPluginWithSuffix: HostSmallApi { } +class SendInts: StreamIntsStreamHandler { + var timerActive = false + var timer: Timer? + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + var count: Int64 = 0 + if !timerActive { + timerActive = true + timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in + DispatchQueue.main.async { + sink.success(count) + count += 1 + if count >= 5 { + sink.endOfStream() + self.timer?.invalidate() + } + } + } + } + } +} + +class SendEvents: StreamEventsStreamHandler { + var timerActive = false + var timer: Timer? + var eventList: [PlatformEvent] = + [ + IntEvent(value: 1), + StringEvent(value: "string"), + BoolEvent(value: false), + DoubleEvent(value: 3.14), + ObjectsEvent(value: true), + EnumEvent(value: EventEnum.fortyTwo), + ClassEvent(value: EventAllNullableTypes(aNullableInt: 0)), + ] + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + var count = 0 + if !timerActive { + timerActive = true + timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in + DispatchQueue.main.async { + if count >= self.eventList.count { + sink.endOfStream() + self.timer?.invalidate() + } else { + sink.success(self.eventList[count]) + count += 1 + } + } + } + } + } +} + class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { func pigeonApiProxyApiTestClass(_ registrar: ProxyApiTestsPigeonProxyApiRegistrar) -> PigeonApiProxyApiTestClass @@ -1221,7 +1279,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { class ProxyApiTestClassDelegate: PigeonApiDelegateProxyApiTestClass { func pigeonDefaultConstructor( pigeonApi: PigeonApiProxyApiTestClass, aBool: Bool, anInt: Int64, aDouble: Double, - aString: String, aUint8List: FlutterStandardTypedData, aList: [Any?], aMap: [String?: Any?], + aString: String, aUint8List: FlutterStandardTypedData, aList: [Any?], + aMap: [String?: Any?], anEnum: ProxyApiTestEnum, aProxyApi: ProxyApiSuperClass, aNullableBool: Bool?, aNullableInt: Int64?, aNullableDouble: Double?, aNullableString: String?, aNullableUint8List: FlutterStandardTypedData?, aNullableList: [Any?]?, @@ -1238,35 +1297,43 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { return ProxyApiTestClass() } - func attachedField(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func attachedField( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> ProxyApiSuperClass { return ProxyApiSuperClass() } - func staticAttachedField(pigeonApi: PigeonApiProxyApiTestClass) throws -> ProxyApiSuperClass { + func staticAttachedField(pigeonApi: PigeonApiProxyApiTestClass) throws + -> ProxyApiSuperClass + { return ProxyApiSuperClass() } - func aBool(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) throws + func aBool(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + throws -> Bool { return true } - func anInt(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) throws + func anInt(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + throws -> Int64 { return 0 } - func aDouble(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) throws + func aDouble(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + throws -> Double { return 0.0 } - func aString(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) throws + func aString(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + throws -> String { return "" @@ -1278,7 +1345,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { return FlutterStandardTypedData(bytes: Data()) } - func aList(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) throws + func aList(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + throws -> [Any?] { return [] @@ -1290,7 +1358,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { return [:] } - func anEnum(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) throws + func anEnum(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + throws -> ProxyApiTestEnum { return ProxyApiTestEnum.one @@ -1302,25 +1371,33 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { return ProxyApiSuperClass() } - func aNullableBool(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableBool( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> Bool? { return nil } - func aNullableInt(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableInt( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> Int64? { return nil } - func aNullableDouble(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableDouble( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> Double? { return nil } - func aNullableString(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableString( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> String? { return nil @@ -1332,19 +1409,25 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { return nil } - func aNullableList(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableList( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> [Any?]? { return nil } - func aNullableMap(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableMap( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> [String?: Any?]? { return nil } - func aNullableEnum(pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass) + func aNullableEnum( + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass + ) throws -> ProxyApiTestEnum? { return nil @@ -1384,7 +1467,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoDouble( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aDouble: Double + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aDouble: Double ) throws -> Double { return aDouble } @@ -1396,7 +1480,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoString( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aString: String + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aString: String ) throws -> String { return aString } @@ -1540,7 +1625,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoAsyncDouble( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aDouble: Double, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aDouble: Double, completion: @escaping (Result) -> Void ) { completion(.success(aDouble)) @@ -1554,7 +1640,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoAsyncString( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aString: String, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aString: String, completion: @escaping (Result) -> Void ) { completion(.success(aString)) @@ -1591,7 +1678,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { func echoAsyncEnum( pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, - anEnum: ProxyApiTestEnum, completion: @escaping (Result) -> Void + anEnum: ProxyApiTestEnum, + completion: @escaping (Result) -> Void ) { completion(.success(anEnum)) } @@ -1628,7 +1716,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoAsyncNullableDouble( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aDouble: Double?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aDouble: Double?, completion: @escaping (Result) -> Void ) { completion(.success(aDouble)) @@ -1642,7 +1731,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoAsyncNullableString( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aString: String?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aString: String?, completion: @escaping (Result) -> Void ) { completion(.success(aString)) @@ -1657,14 +1747,16 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func echoAsyncNullableObject( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, anObject: Any?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + anObject: Any?, completion: @escaping (Result) -> Void ) { completion(.success(anObject)) } func echoAsyncNullableList( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aList: [Any?]?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void ) { completion(.success(aList)) @@ -1679,7 +1771,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { func echoAsyncNullableEnum( pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, - anEnum: ProxyApiTestEnum?, completion: @escaping (Result) -> Void + anEnum: ProxyApiTestEnum?, + completion: @escaping (Result) -> Void ) { completion(.success(anEnum)) } @@ -1688,13 +1781,15 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } - func echoStaticString(pigeonApi: PigeonApiProxyApiTestClass, aString: String) throws -> String + func echoStaticString(pigeonApi: PigeonApiProxyApiTestClass, aString: String) throws + -> String { return aString } func staticAsyncNoop( - pigeonApi: PigeonApiProxyApiTestClass, completion: @escaping (Result) -> Void + pigeonApi: PigeonApiProxyApiTestClass, + completion: @escaping (Result) -> Void ) { completion(.success(Void())) } @@ -1770,10 +1865,12 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func callFlutterEchoDouble( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aDouble: Double, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aDouble: Double, completion: @escaping (Result) -> Void ) { - pigeonApi.flutterEchoDouble(pigeonInstance: pigeonInstance, aDouble: aDouble) { response in + pigeonApi.flutterEchoDouble(pigeonInstance: pigeonInstance, aDouble: aDouble) { + response in switch response { case .success(let res): completion(.success(res)) @@ -1784,10 +1881,12 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func callFlutterEchoString( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aString: String, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aString: String, completion: @escaping (Result) -> Void ) { - pigeonApi.flutterEchoString(pigeonInstance: pigeonInstance, aString: aString) { response in + pigeonApi.flutterEchoString(pigeonInstance: pigeonInstance, aString: aString) { + response in switch response { case .success(let res): completion(.success(res)) @@ -1862,7 +1961,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { aMap: [String?: ProxyApiTestClass?], completion: @escaping (Result<[String?: ProxyApiTestClass?], Error>) -> Void ) { - pigeonApi.flutterEchoProxyApiMap(pigeonInstance: pigeonInstance, aMap: aMap) { response in + pigeonApi.flutterEchoProxyApiMap(pigeonInstance: pigeonInstance, aMap: aMap) { + response in switch response { case .success(let res): completion(.success(res)) @@ -1874,7 +1974,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { func callFlutterEchoEnum( pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, - anEnum: ProxyApiTestEnum, completion: @escaping (Result) -> Void + anEnum: ProxyApiTestEnum, + completion: @escaping (Result) -> Void ) { pigeonApi.flutterEchoEnum(pigeonInstance: pigeonInstance, anEnum: anEnum) { response in switch response { @@ -1921,7 +2022,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, anInt: Int64?, completion: @escaping (Result) -> Void ) { - pigeonApi.flutterEchoNullableInt(pigeonInstance: pigeonInstance, anInt: anInt) { response in + pigeonApi.flutterEchoNullableInt(pigeonInstance: pigeonInstance, anInt: anInt) { + response in switch response { case .success(let res): completion(.success(res)) @@ -1932,7 +2034,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func callFlutterEchoNullableDouble( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aDouble: Double?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aDouble: Double?, completion: @escaping (Result) -> Void ) { pigeonApi.flutterEchoNullableDouble(pigeonInstance: pigeonInstance, aDouble: aDouble) { @@ -1947,7 +2050,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func callFlutterEchoNullableString( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aString: String?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aString: String?, completion: @escaping (Result) -> Void ) { pigeonApi.flutterEchoNullableString(pigeonInstance: pigeonInstance, aString: aString) { @@ -1966,7 +2070,9 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { aUint8List: FlutterStandardTypedData?, completion: @escaping (Result) -> Void ) { - pigeonApi.flutterEchoNullableUint8List(pigeonInstance: pigeonInstance, aList: aUint8List) { + pigeonApi.flutterEchoNullableUint8List( + pigeonInstance: pigeonInstance, aList: aUint8List + ) { response in switch response { case .success(let res): @@ -1978,7 +2084,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func callFlutterEchoNullableList( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aList: [Any?]?, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void ) { pigeonApi.flutterEchoNullableList(pigeonInstance: pigeonInstance, aList: aList) { @@ -1996,7 +2103,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void ) { - pigeonApi.flutterEchoNullableMap(pigeonInstance: pigeonInstance, aMap: aMap) { response in + pigeonApi.flutterEchoNullableMap(pigeonInstance: pigeonInstance, aMap: aMap) { + response in switch response { case .success(let res): completion(.success(res)) @@ -2008,7 +2116,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { func callFlutterEchoNullableEnum( pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, - anEnum: ProxyApiTestEnum?, completion: @escaping (Result) -> Void + anEnum: ProxyApiTestEnum?, + completion: @escaping (Result) -> Void ) { pigeonApi.flutterEchoNullableEnum(pigeonInstance: pigeonInstance, anEnum: anEnum) { response in @@ -2026,8 +2135,9 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { aProxyApi: ProxyApiSuperClass?, completion: @escaping (Result) -> Void ) { - pigeonApi.flutterEchoNullableProxyApi(pigeonInstance: pigeonInstance, aProxyApi: aProxyApi) - { response in + pigeonApi.flutterEchoNullableProxyApi( + pigeonInstance: pigeonInstance, aProxyApi: aProxyApi + ) { response in switch response { case .success(let res): completion(.success(res)) @@ -2052,7 +2162,8 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { } func callFlutterEchoAsyncString( - pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, aString: String, + pigeonApi: PigeonApiProxyApiTestClass, pigeonInstance: ProxyApiTestClass, + aString: String, completion: @escaping (Result) -> Void ) { pigeonApi.flutterEchoAsyncString(pigeonInstance: pigeonInstance, aString: aString) { @@ -2081,7 +2192,9 @@ class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { return ProxyApiSuperClass() } - func aSuperMethod(pigeonApi: PigeonApiProxyApiSuperClass, pigeonInstance: ProxyApiSuperClass) + func aSuperMethod( + pigeonApi: PigeonApiProxyApiSuperClass, pigeonInstance: ProxyApiSuperClass + ) throws {} } diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/.gitignore b/packages/pigeon/platform_tests/test_plugin/macos/Classes/.gitignore index 7a58c79195bb..68f71f9d95f6 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/.gitignore +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/.gitignore @@ -6,4 +6,5 @@ # Keeping this makes it easier to review changes to ProxyApi generation. !ProxyApiTests.gen.swift # Contains the class declartions for testing ProxyApis. -!ProxyApiTestClass.swift \ No newline at end of file +!ProxyApiTestClass.swift +!EventChannelTests.gen.swift \ No newline at end of file diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift index 2b2001d6aa44..a90e8fa07a5d 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift @@ -15,24 +15,6 @@ import Foundation #error("Unsupported platform.") #endif -/// Error class for passing custom error details to Dart side. -final class PigeonError: Error { - let code: String - let message: String? - let details: Any? - - init(code: String, message: String?, details: Any?) { - self.code = code - self.message = message - self.details = details - } - - var localizedDescription: String { - return - "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } -} - private func wrapResult(_ result: Any?) -> [Any?] { return [result] } diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/EventChannelTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/EventChannelTests.gen.swift new file mode 100644 index 000000000000..581253256bc4 --- /dev/null +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/EventChannelTests.gen.swift @@ -0,0 +1,576 @@ +// 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 + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class EventChannelTestsError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "EventChannelTestsError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +enum EventEnum: Int { + case one = 0 + case two = 1 + case three = 2 + case fortyTwo = 3 + case fourHundredTwentyTwo = 4 +} + +enum AnotherEventEnum: Int { + case justInCase = 0 +} + +/// A class containing all supported nullable types. +/// +/// Generated class from Pigeon that represents data sent in messages. +class EventAllNullableTypes { + init( + aNullableBool: Bool? = nil, + aNullableInt: Int64? = nil, + aNullableInt64: Int64? = nil, + aNullableDouble: Double? = nil, + aNullableByteArray: FlutterStandardTypedData? = nil, + aNullable4ByteArray: FlutterStandardTypedData? = nil, + aNullable8ByteArray: FlutterStandardTypedData? = nil, + aNullableFloatArray: FlutterStandardTypedData? = nil, + aNullableEnum: EventEnum? = nil, + anotherNullableEnum: AnotherEventEnum? = nil, + aNullableString: String? = nil, + aNullableObject: Any? = nil, + allNullableTypes: EventAllNullableTypes? = nil, + list: [Any?]? = nil, + stringList: [String?]? = nil, + intList: [Int64?]? = nil, + doubleList: [Double?]? = nil, + boolList: [Bool?]? = nil, + enumList: [EventEnum?]? = nil, + objectList: [Any?]? = nil, + listList: [[Any?]?]? = nil, + mapList: [[AnyHashable?: Any?]?]? = nil, + recursiveClassList: [EventAllNullableTypes?]? = nil, + map: [AnyHashable?: Any?]? = nil, + stringMap: [String?: String?]? = nil, + intMap: [Int64?: Int64?]? = nil, + enumMap: [EventEnum?: EventEnum?]? = nil, + objectMap: [AnyHashable?: Any?]? = nil, + listMap: [Int64?: [Any?]?]? = nil, + mapMap: [Int64?: [AnyHashable?: Any?]?]? = nil, + recursiveClassMap: [Int64?: EventAllNullableTypes?]? = nil + ) { + self.aNullableBool = aNullableBool + self.aNullableInt = aNullableInt + self.aNullableInt64 = aNullableInt64 + self.aNullableDouble = aNullableDouble + self.aNullableByteArray = aNullableByteArray + self.aNullable4ByteArray = aNullable4ByteArray + self.aNullable8ByteArray = aNullable8ByteArray + self.aNullableFloatArray = aNullableFloatArray + self.aNullableEnum = aNullableEnum + self.anotherNullableEnum = anotherNullableEnum + self.aNullableString = aNullableString + self.aNullableObject = aNullableObject + self.allNullableTypes = allNullableTypes + self.list = list + self.stringList = stringList + self.intList = intList + self.doubleList = doubleList + self.boolList = boolList + self.enumList = enumList + self.objectList = objectList + self.listList = listList + self.mapList = mapList + self.recursiveClassList = recursiveClassList + self.map = map + self.stringMap = stringMap + self.intMap = intMap + self.enumMap = enumMap + self.objectMap = objectMap + self.listMap = listMap + self.mapMap = mapMap + self.recursiveClassMap = recursiveClassMap + } + var aNullableBool: Bool? + var aNullableInt: Int64? + var aNullableInt64: Int64? + var aNullableDouble: Double? + var aNullableByteArray: FlutterStandardTypedData? + var aNullable4ByteArray: FlutterStandardTypedData? + var aNullable8ByteArray: FlutterStandardTypedData? + var aNullableFloatArray: FlutterStandardTypedData? + var aNullableEnum: EventEnum? + var anotherNullableEnum: AnotherEventEnum? + var aNullableString: String? + var aNullableObject: Any? + var allNullableTypes: EventAllNullableTypes? + var list: [Any?]? + var stringList: [String?]? + var intList: [Int64?]? + var doubleList: [Double?]? + var boolList: [Bool?]? + var enumList: [EventEnum?]? + var objectList: [Any?]? + var listList: [[Any?]?]? + var mapList: [[AnyHashable?: Any?]?]? + var recursiveClassList: [EventAllNullableTypes?]? + var map: [AnyHashable?: Any?]? + var stringMap: [String?: String?]? + var intMap: [Int64?: Int64?]? + var enumMap: [EventEnum?: EventEnum?]? + var objectMap: [AnyHashable?: Any?]? + var listMap: [Int64?: [Any?]?]? + var mapMap: [Int64?: [AnyHashable?: Any?]?]? + var recursiveClassMap: [Int64?: EventAllNullableTypes?]? + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> EventAllNullableTypes? { + let aNullableBool: Bool? = nilOrValue(pigeonVar_list[0]) + let aNullableInt: Int64? = nilOrValue(pigeonVar_list[1]) + let aNullableInt64: Int64? = nilOrValue(pigeonVar_list[2]) + let aNullableDouble: Double? = nilOrValue(pigeonVar_list[3]) + let aNullableByteArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[4]) + let aNullable4ByteArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[5]) + let aNullable8ByteArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[6]) + let aNullableFloatArray: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[7]) + let aNullableEnum: EventEnum? = nilOrValue(pigeonVar_list[8]) + let anotherNullableEnum: AnotherEventEnum? = nilOrValue(pigeonVar_list[9]) + let aNullableString: String? = nilOrValue(pigeonVar_list[10]) + let aNullableObject: Any? = pigeonVar_list[11] + let allNullableTypes: EventAllNullableTypes? = nilOrValue(pigeonVar_list[12]) + let list: [Any?]? = nilOrValue(pigeonVar_list[13]) + let stringList: [String?]? = nilOrValue(pigeonVar_list[14]) + let intList: [Int64?]? = nilOrValue(pigeonVar_list[15]) + let doubleList: [Double?]? = nilOrValue(pigeonVar_list[16]) + let boolList: [Bool?]? = nilOrValue(pigeonVar_list[17]) + let enumList: [EventEnum?]? = nilOrValue(pigeonVar_list[18]) + let objectList: [Any?]? = nilOrValue(pigeonVar_list[19]) + let listList: [[Any?]?]? = nilOrValue(pigeonVar_list[20]) + let mapList: [[AnyHashable?: Any?]?]? = nilOrValue(pigeonVar_list[21]) + let recursiveClassList: [EventAllNullableTypes?]? = nilOrValue(pigeonVar_list[22]) + let map: [AnyHashable?: Any?]? = nilOrValue(pigeonVar_list[23]) + let stringMap: [String?: String?]? = nilOrValue(pigeonVar_list[24]) + let intMap: [Int64?: Int64?]? = nilOrValue(pigeonVar_list[25]) + let enumMap: [EventEnum?: EventEnum?]? = pigeonVar_list[26] as? [EventEnum?: EventEnum?] + let objectMap: [AnyHashable?: Any?]? = nilOrValue(pigeonVar_list[27]) + let listMap: [Int64?: [Any?]?]? = nilOrValue(pigeonVar_list[28]) + let mapMap: [Int64?: [AnyHashable?: Any?]?]? = nilOrValue(pigeonVar_list[29]) + let recursiveClassMap: [Int64?: EventAllNullableTypes?]? = nilOrValue(pigeonVar_list[30]) + + return EventAllNullableTypes( + aNullableBool: aNullableBool, + aNullableInt: aNullableInt, + aNullableInt64: aNullableInt64, + aNullableDouble: aNullableDouble, + aNullableByteArray: aNullableByteArray, + aNullable4ByteArray: aNullable4ByteArray, + aNullable8ByteArray: aNullable8ByteArray, + aNullableFloatArray: aNullableFloatArray, + aNullableEnum: aNullableEnum, + anotherNullableEnum: anotherNullableEnum, + aNullableString: aNullableString, + aNullableObject: aNullableObject, + allNullableTypes: allNullableTypes, + list: list, + stringList: stringList, + intList: intList, + doubleList: doubleList, + boolList: boolList, + enumList: enumList, + objectList: objectList, + listList: listList, + mapList: mapList, + recursiveClassList: recursiveClassList, + map: map, + stringMap: stringMap, + intMap: intMap, + enumMap: enumMap, + objectMap: objectMap, + listMap: listMap, + mapMap: mapMap, + recursiveClassMap: recursiveClassMap + ) + } + func toList() -> [Any?] { + return [ + aNullableBool, + aNullableInt, + aNullableInt64, + aNullableDouble, + aNullableByteArray, + aNullable4ByteArray, + aNullable8ByteArray, + aNullableFloatArray, + aNullableEnum, + anotherNullableEnum, + aNullableString, + aNullableObject, + allNullableTypes, + list, + stringList, + intList, + doubleList, + boolList, + enumList, + objectList, + listList, + mapList, + recursiveClassList, + map, + stringMap, + intMap, + enumMap, + objectMap, + listMap, + mapMap, + recursiveClassMap, + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +/// This class should not be extended by any user class outside of the generated file. +protocol PlatformEvent { + +} + +/// Generated class from Pigeon that represents data sent in messages. +struct IntEvent: PlatformEvent { + var value: Int64 + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> IntEvent? { + let value = pigeonVar_list[0] as! Int64 + + return IntEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct StringEvent: PlatformEvent { + var value: String + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> StringEvent? { + let value = pigeonVar_list[0] as! String + + return StringEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct BoolEvent: PlatformEvent { + var value: Bool + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> BoolEvent? { + let value = pigeonVar_list[0] as! Bool + + return BoolEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct DoubleEvent: PlatformEvent { + var value: Double + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> DoubleEvent? { + let value = pigeonVar_list[0] as! Double + + return DoubleEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct ObjectsEvent: PlatformEvent { + var value: Any + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> ObjectsEvent? { + let value = pigeonVar_list[0]! + + return ObjectsEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct EnumEvent: PlatformEvent { + var value: EventEnum + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> EnumEvent? { + let value = pigeonVar_list[0] as! EventEnum + + return EnumEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct ClassEvent: PlatformEvent { + var value: EventAllNullableTypes + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> ClassEvent? { + let value = pigeonVar_list[0] as! EventAllNullableTypes + + return ClassEvent( + value: value + ) + } + func toList() -> [Any?] { + return [ + value + ] + } +} + +private class EventChannelTestsPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return EventEnum(rawValue: enumResultAsInt) + } + return nil + case 130: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return AnotherEventEnum(rawValue: enumResultAsInt) + } + return nil + case 131: + return EventAllNullableTypes.fromList(self.readValue() as! [Any?]) + case 132: + return IntEvent.fromList(self.readValue() as! [Any?]) + case 133: + return StringEvent.fromList(self.readValue() as! [Any?]) + case 134: + return BoolEvent.fromList(self.readValue() as! [Any?]) + case 135: + return DoubleEvent.fromList(self.readValue() as! [Any?]) + case 136: + return ObjectsEvent.fromList(self.readValue() as! [Any?]) + case 137: + return EnumEvent.fromList(self.readValue() as! [Any?]) + case 138: + return ClassEvent.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class EventChannelTestsPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? EventEnum { + super.writeByte(129) + super.writeValue(value.rawValue) + } else if let value = value as? AnotherEventEnum { + super.writeByte(130) + super.writeValue(value.rawValue) + } else if let value = value as? EventAllNullableTypes { + super.writeByte(131) + super.writeValue(value.toList()) + } else if let value = value as? IntEvent { + super.writeByte(132) + super.writeValue(value.toList()) + } else if let value = value as? StringEvent { + super.writeByte(133) + super.writeValue(value.toList()) + } else if let value = value as? BoolEvent { + super.writeByte(134) + super.writeValue(value.toList()) + } else if let value = value as? DoubleEvent { + super.writeByte(135) + super.writeValue(value.toList()) + } else if let value = value as? ObjectsEvent { + super.writeByte(136) + super.writeValue(value.toList()) + } else if let value = value as? EnumEvent { + super.writeByte(137) + super.writeValue(value.toList()) + } else if let value = value as? ClassEvent { + super.writeByte(138) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class EventChannelTestsPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return EventChannelTestsPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return EventChannelTestsPigeonCodecWriter(data: data) + } +} + +class EventChannelTestsPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = EventChannelTestsPigeonCodec( + readerWriter: EventChannelTestsPigeonCodecReaderWriter()) +} + +var eventChannelTestsPigeonMethodCodec = FlutterStandardMethodCodec( + readerWriter: EventChannelTestsPigeonCodecReaderWriter()) + +private class PigeonStreamHandler: NSObject, FlutterStreamHandler { + private let wrapper: PigeonEventChannelWrapper + private var pigeonSink: PigeonEventSink? = nil + + init(wrapper: PigeonEventChannelWrapper) { + self.wrapper = wrapper + } + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + pigeonSink = PigeonEventSink(events) + wrapper.onListen(withArguments: arguments, sink: pigeonSink!) + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + pigeonSink = nil + wrapper.onCancel(withArguments: arguments) + return nil + } +} + +class PigeonEventChannelWrapper { + func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {} + func onCancel(withArguments arguments: Any?) {} +} + +class PigeonEventSink { + private let sink: FlutterEventSink + + init(_ sink: @escaping FlutterEventSink) { + self.sink = sink + } + + func success(_ value: ReturnType) { + sink(value) + } + + func error(code: String, message: String?, details: Any?) { + sink(FlutterError(code: code, message: message, details: details)) + } + + func endOfStream() { + sink(FlutterEndOfEventStream) + } + +} + +class StreamIntsStreamHandler: PigeonEventChannelWrapper { + static func register( + with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamIntsStreamHandler + ) { + var channelName = "dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamInts" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(streamHandler: wrapper) + let channel = FlutterEventChannel( + name: channelName, binaryMessenger: messenger, codec: eventChannelTestsPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} + +class StreamEventsStreamHandler: PigeonEventChannelWrapper { + static func register( + with messenger: FlutterBinaryMessenger, + instanceName: String = "", + streamHandler: StreamEventsStreamHandler + ) { + var channelName = "dev.flutter.pigeon.pigeon_integration_tests.EventChannelCoreApi.streamEvents" + if !instanceName.isEmpty { + channelName += ".\(instanceName)" + } + let internalStreamHandler = PigeonStreamHandler(streamHandler: wrapper) + let channel = FlutterEventChannel( + name: channelName, binaryMessenger: messenger, codec: eventChannelTestsPigeonMethodCodec) + channel.setStreamHandler(internalStreamHandler) + } +} diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift index fb5c21dabf18..3d5dc4c0d93a 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift @@ -30,6 +30,9 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { proxyApiRegistrar = ProxyApiTestsPigeonProxyApiRegistrar( binaryMessenger: binaryMessenger, apiDelegate: ProxyApiDelegate()) proxyApiRegistrar!.setUp() + + StreamIntsStreamHandler.register(with: binaryMessenger, wrapper: SendInts()) + StreamEventsStreamHandler.register(with: binaryMessenger, wrapper: SendEvents()) } public func detachFromEngine(for registrar: FlutterPluginRegistrar) { @@ -1212,6 +1215,60 @@ public class TestPluginWithSuffix: HostSmallApi { } } +class SendInts: StreamIntsStreamHandler { + var timerActive = false + var timer: Timer? + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + var count: Int64 = 0 + if !timerActive { + timerActive = true + timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in + DispatchQueue.main.async { + sink.success(count) + count += 1 + if count >= 5 { + sink.endOfStream() + self.timer?.invalidate() + } + } + } + } + } +} + +class SendEvents: StreamEventsStreamHandler { + var timerActive = false + var timer: Timer? + var eventList: [PlatformEvent] = + [ + IntEvent(value: 1), + StringEvent(value: "string"), + BoolEvent(value: false), + DoubleEvent(value: 3.14), + ObjectsEvent(value: true), + EnumEvent(value: EventEnum.fortyTwo), + ClassEvent(value: EventAllNullableTypes(aNullableInt: 0)), + ] + + override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) { + var count = 0 + if !timerActive { + timerActive = true + timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in + DispatchQueue.main.async { + if count >= self.eventList.count { + sink.endOfStream() + self.timer?.invalidate() + } else { + sink.success(self.eventList[count]) + count += 1 + } + } + } + } + } +} class ProxyApiDelegate: ProxyApiTestsPigeonProxyApiDelegate { func pigeonApiProxyApiTestClass(_ registrar: ProxyApiTestsPigeonProxyApiRegistrar) diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 76b8cecce380..a7b5056bac10 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: 22.6.2 # This must match the version in lib/generator_tools.dart +version: 22.7.0 # This must match the version in lib/generator_tools.dart environment: sdk: ^3.3.0 diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index 65a8f7ed8fdf..c88311f4c5be 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -249,23 +249,29 @@ void main() { }); test('Error field is private with public accessors', () { - final Root root = Root(apis: [ - AstHostApi(name: 'Api', methods: [ - Method( - name: 'doSomething', - location: ApiLocation.host, - parameters: [ - Parameter( - type: const TypeDeclaration( - baseName: 'int', - isNullable: false, - ), - name: 'someInput') - ], - returnType: const TypeDeclaration(baseName: 'int', isNullable: false), - ) - ]) - ], classes: [], enums: []); + final Root root = Root( + apis: [ + AstHostApi(name: 'Api', methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'int', + isNullable: false, + ), + name: 'someInput') + ], + returnType: + const TypeDeclaration(baseName: 'int', isNullable: false), + ) + ]) + ], + classes: [], + enums: [], + containsHostApi: true, + ); { final StringBuffer sink = StringBuffer(); const CppGenerator generator = CppGenerator(); @@ -2070,6 +2076,7 @@ void main() { ], classes: [], enums: [], + containsFlutterApi: true, ); final StringBuffer sink = StringBuffer(); const CppGenerator generator = CppGenerator(); diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 4dca4b416844..7a4d44c26177 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -667,7 +667,7 @@ void main() { expect(code, matches('pigeonVar_channel.send[(]null[)]')); }); - test('mock dart handler', () { + test('mock Dart handler', () { final Root root = Root(apis: [ AstHostApi(name: 'Api', dartHostTestHandler: 'ApiMock', methods: [ Method( @@ -1711,17 +1711,22 @@ name: foobar }); test('connection error contains channel name', () { - final Root root = Root(apis: [ - AstHostApi(name: 'Api', methods: [ - Method( - name: 'method', - location: ApiLocation.host, - parameters: [], - returnType: - const TypeDeclaration(baseName: 'Output', isNullable: false), - ) - ]) - ], classes: [], enums: []); + final Root root = Root( + apis: [ + AstHostApi(name: 'Api', methods: [ + Method( + name: 'method', + location: ApiLocation.host, + parameters: [], + returnType: + const TypeDeclaration(baseName: 'Output', isNullable: false), + ) + ]) + ], + classes: [], + enums: [], + containsHostApi: true, + ); final StringBuffer sink = StringBuffer(); const DartGenerator generator = DartGenerator(); generator.generate( diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index 63a6e8330eab..49358cf7ddaa 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -121,45 +121,50 @@ void main() { }); test('gen one host api', () { - final Root root = Root(apis: [ - AstHostApi(name: 'Api', methods: [ - Method( - name: 'doSomething', - location: ApiLocation.host, - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Input', - associatedClass: emptyClass, - isNullable: false, - ), - name: '') - ], - returnType: TypeDeclaration( - baseName: 'Output', - associatedClass: emptyClass, - isNullable: false, - ), - ) - ]) - ], classes: [ - Class(name: 'Input', fields: [ - NamedType( - type: const TypeDeclaration( - baseName: 'String', - isNullable: true, - ), - name: 'input') - ]), - Class(name: 'Output', fields: [ - NamedType( - type: const TypeDeclaration( - baseName: 'String', - isNullable: true, + final Root root = Root( + apis: [ + AstHostApi(name: 'Api', methods: [ + Method( + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + associatedClass: emptyClass, + isNullable: false, + ), + name: '') + ], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, ), - name: 'output') - ]) - ], enums: []); + ) + ]) + ], + classes: [ + Class(name: 'Input', fields: [ + NamedType( + type: const TypeDeclaration( + baseName: 'String', + isNullable: true, + ), + name: 'input') + ]), + Class(name: 'Output', fields: [ + NamedType( + type: const TypeDeclaration( + baseName: 'String', + isNullable: true, + ), + name: 'output') + ]) + ], + enums: [], + containsHostApi: true, + ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); const JavaGenerator generator = JavaGenerator(); @@ -1565,6 +1570,7 @@ void main() { apis: [api], classes: [], enums: [], + containsHostApi: true, ); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -1604,6 +1610,7 @@ void main() { ], classes: [], enums: [], + containsFlutterApi: true, ); final StringBuffer sink = StringBuffer(); const JavaGenerator generator = JavaGenerator(); diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index 549601b8851d..6043229fd729 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -1533,6 +1533,7 @@ void main() { apis: [api], classes: [], enums: [], + containsHostApi: true, ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = @@ -1577,6 +1578,7 @@ void main() { ], classes: [], enums: [], + containsFlutterApi: true, ); final StringBuffer sink = StringBuffer(); const KotlinOptions kotlinOptions = KotlinOptions(); diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index 734145e2c24b..728bb50ac9c7 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -2861,6 +2861,7 @@ void main() { ], classes: [], enums: [], + containsFlutterApi: true, ); final StringBuffer sink = StringBuffer(); const ObjcGenerator generator = ObjcGenerator(); diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 9e3fa213fc78..bf6fc7cbca16 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -232,6 +232,39 @@ abstract class Api { expect(results.errors[0].message, contains('dynamic')); }); + test('Only allow one api annotation', () { + const String source = ''' +@HostApi() +@FlutterApi() +abstract class Api { + int foo(); +} +'''; + final ParseResults results = parseSource(source); + expect(results.errors.length, 1); + expect( + results.errors[0].message, + contains( + 'API "Api" can only have one API annotation but contains: [@HostApi(), @FlutterApi()]')); + }); + + test('Only allow one api annotation plus @ConfigurePigeon', () { + const String source = ''' +@ConfigurePigeon(PigeonOptions( + dartOut: 'stdout', + javaOut: 'stdout', + dartOptions: DartOptions(), +)) +@HostApi() +abstract class Api { + void ping(); +} + +'''; + final ParseResults results = parseSource(source); + expect(results.errors.length, 0); + }); + test('enum in classes', () { const String code = ''' enum Enum1 { @@ -1567,4 +1600,69 @@ abstract class MyClass { ); }); }); + + group('event channel validation', () { + test('methods cannot contain parameters', () { + const String code = ''' +@EventChannelApi() +abstract class EventChannelApi { + int streamInts(int event); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors.length, equals(1)); + expect( + parseResult.errors.single.message, + contains( + 'event channel methods must not be contain parameters, in method "streamInts" in API: "EventChannelApi"'), + ); + }); + }); + + group('sealed inheritance validation', () { + test('super class must be sealed', () { + const String code = ''' +class DataClass {} +class ChildClass extends DataClass { + ChildClass(this.input); + int input; +} + +@EventChannelApi() +abstract class events { + void aMethod(ChildClass param); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Child class: "ChildClass" must extend a sealed class.'), + ); + }); + + test('super class must be sealed', () { + const String code = ''' +sealed class DataClass { + DataClass(this.input); + int input; +} +class ChildClass extends DataClass { + ChildClass(this.input); + int input; +} + +@EventChannelApi() +abstract class events { + void aMethod(ChildClass param); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Sealed class: "DataClass" must not contain fields.'), + ); + }); + }); } diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index b9f7de5d356a..6ad36f934ecc 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -1508,6 +1508,7 @@ void main() { ], classes: [], enums: [], + containsFlutterApi: true, ); final StringBuffer sink = StringBuffer(); const SwiftOptions kotlinOptions = SwiftOptions(); diff --git a/packages/pigeon/tool/shared/generation.dart b/packages/pigeon/tool/shared/generation.dart index 4cee0d4de568..a553c08425a1 100644 --- a/packages/pigeon/tool/shared/generation.dart +++ b/packages/pigeon/tool/shared/generation.dart @@ -23,7 +23,20 @@ enum GeneratorLanguage { // A map of pigeons/ files to the languages that they can't yet be generated // for due to limitations of that generator. const Map> _unsupportedFiles = - >{}; + >{ + 'event_channel_tests': { + GeneratorLanguage.cpp, + GeneratorLanguage.gobject, + GeneratorLanguage.java, + GeneratorLanguage.objc, + }, + 'proxy_api_tests': { + GeneratorLanguage.cpp, + GeneratorLanguage.gobject, + GeneratorLanguage.java, + GeneratorLanguage.objc, + }, +}; String _snakeToPascalCase(String snake) { final List parts = snake.split('_'); @@ -48,21 +61,31 @@ String _javaFilenameForName(String inputName) { } Future generateExamplePigeons() async { - return runPigeon( + int success = 0; + success = await runPigeon( input: './example/app/pigeons/messages.dart', basePath: './example/app', suppressVersion: true, ); + success += await runPigeon( + input: './example/app/pigeons/event_channel_messages.dart', + basePath: './example/app', + suppressVersion: true, + ); + return success; } Future generateTestPigeons( {required String baseDir, bool includeOverflow = false}) async { // TODO(stuartmorgan): Make this dynamic rather than hard-coded. Or eliminate // it entirely; see https://github.com/flutter/flutter/issues/115169. - const List inputs = [ + + /// A list of all pigeons to be generated along with a list of skipped languages. + const Set inputs = { 'background_platform_channels', 'core_tests', 'enum', + 'event_channel_tests', 'flutter_unittests', // Only for Dart unit tests in shared_test_plugin_code 'message', 'multiple_arity', @@ -71,7 +94,7 @@ Future generateTestPigeons( 'nullable_returns', 'primitive', 'proxy_api_tests', - ]; + }; final String outputBase = p.join(baseDir, 'platform_tests', 'test_plugin'); final String alternateOutputBase = diff --git a/script/tool/lib/src/podspec_check_command.dart b/script/tool/lib/src/podspec_check_command.dart index e13a6cb02cac..01ff7d04fe21 100644 --- a/script/tool/lib/src/podspec_check_command.dart +++ b/script/tool/lib/src/podspec_check_command.dart @@ -85,7 +85,7 @@ class PodspecCheckCommand extends PackageLoopingCommand { '''; final String path = getRelativePosixPath(podspec, from: package.directory); - printError('$path is missing seach path configuration. Any iOS ' + printError('$path is missing search path configuration. Any iOS ' 'plugin implementation that contains Swift implementation code ' 'needs to contain the following:\n\n' '$workaroundBlock\n' @@ -110,12 +110,18 @@ class PodspecCheckCommand extends PackageLoopingCommand { } Future> _podspecsToLint(RepositoryPackage package) async { + // Since the pigeon platform tests podspecs require generated files that are not included in git, + // the podspec lint fails. + if (package.path.contains('packages/pigeon/platform_tests/')) { + return []; + } final List podspecs = await getFilesForPackage(package).where((File entity) { final String filename = entity.basename; return path.extension(filename) == '.podspec' && filename != 'Flutter.podspec' && - filename != 'FlutterMacOS.podspec'; + filename != 'FlutterMacOS.podspec' && + !entity.path.contains('packages/pigeon/platform_tests/'); }).toList(); podspecs.sort((File a, File b) => a.basename.compareTo(b.basename)); @@ -161,7 +167,7 @@ class PodspecCheckCommand extends PackageLoopingCommand { } /// Returns true if there is any iOS plugin implementation code written in - /// Swift. Skips files named "Package.swift", which is a Swift Pacakge Manager + /// Swift. Skips files named "Package.swift", which is a Swift Package Manager /// manifest file and does not mean the plugin is written in Swift. Future _hasIOSSwiftCode(RepositoryPackage package) async { final String iosSwiftPackageManifestPath = package