From 583d6d787007d7c5eb30e46f32fc64544e87acc3 Mon Sep 17 00:00:00 2001 From: Fedor Blagodyr Date: Tue, 24 Dec 2024 01:00:23 +0300 Subject: [PATCH] added support of modern async api Swift/Kotlin --- packages/pigeon/CHANGELOG.md | 1 + .../java/io/flutter/plugins/Messages.java | 34 +++ .../pigeon_example_app/MainActivity.kt | 19 +- .../flutter/pigeon_example_app/Messages.g.kt | 33 ++- packages/pigeon/example/app/ios/Podfile | 2 +- .../app/ios/Runner.xcodeproj/project.pbxproj | 23 +- .../example/app/ios/Runner/AppDelegate.swift | 10 + .../example/app/ios/Runner/Messages.g.swift | 25 +++ packages/pigeon/example/app/lib/main.dart | 41 +++- .../example/app/lib/src/messages.g.dart | 29 +++ .../pigeon/example/app/linux/messages.g.cc | 147 +++++++++++++ .../pigeon/example/app/linux/messages.g.h | 28 +++ .../example/app/linux/my_application.cc | 20 +- packages/pigeon/example/app/macos/Podfile | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 4 +- .../app/macos/Runner/MainFlutterWindow.swift | 10 + .../example/app/macos/Runner/messages.g.h | 3 + .../example/app/macos/Runner/messages.g.m | 26 +++ .../pigeon/example/app/pigeons/messages.dart | 3 + .../app/windows/runner/flutter_window.cpp | 9 + .../example/app/windows/runner/messages.g.cpp | 38 ++++ .../example/app/windows/runner/messages.g.h | 3 + packages/pigeon/lib/ast.dart | 44 +++- packages/pigeon/lib/kotlin_generator.dart | 55 +++-- packages/pigeon/lib/pigeon_lib.dart | 57 ++++- packages/pigeon/lib/swift_generator.dart | 81 +++++--- packages/pigeon/test/cpp_generator_test.dart | 10 +- packages/pigeon/test/dart_generator_test.dart | 44 ++-- packages/pigeon/test/java_generator_test.dart | 21 +- .../pigeon/test/kotlin_generator_test.dart | 71 ++++++- packages/pigeon/test/objc_generator_test.dart | 196 +++++++++--------- .../pigeon/test/swift_generator_test.dart | 70 ++++++- .../src/common/package_looping_command.dart | 4 +- .../src/create_all_packages_app_command.dart | 4 +- .../tool/lib/src/pubspec_check_command.dart | 4 +- .../tool/lib/src/update_min_sdk_command.dart | 2 +- .../create_all_packages_app_command_test.dart | 2 +- .../test/update_min_sdk_command_test.dart | 14 +- 38 files changed, 976 insertions(+), 213 deletions(-) diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index e8226fe98562..6d7b172ccedd 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Added support of modern asynchronous api for Swift (async) and Kotlin (suspend). ## 22.7.0 diff --git a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java index 8d9256c3795e..a5ef30585077 100644 --- a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java +++ b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java @@ -290,6 +290,8 @@ public interface ExampleHostApi { void sendMessage(@NonNull MessageData message, @NonNull Result result); + void sendMessageModernAsync(@NonNull MessageData message, @NonNull Result result); + /** The codec used by ExampleHostApi. */ static @NonNull MessageCodec getCodec() { return PigeonCodec.INSTANCE; @@ -385,6 +387,38 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + MessageData messageArg = (MessageData) args.get(0); + Result resultCallback = + new Result() { + public void success(Boolean result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.sendMessageModernAsync(messageArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ 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 0b9e4a1c534d..b1efd59b20c6 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 @@ -18,6 +18,10 @@ import android.os.Looper import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.plugins.FlutterPlugin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay // #docregion kotlin-class private class PigeonApiImplementation : ExampleHostApi { @@ -39,6 +43,16 @@ private class PigeonApiImplementation : ExampleHostApi { } callback(Result.success(true)) } + + override suspend fun sendMessageModernAsync(message: MessageData): Boolean { + if (message.code == Code.ONE) { + throw FlutterError("code", "message", "details") + } + + delay(2000) + + return true + } } // #enddocregion kotlin-class @@ -111,7 +125,10 @@ class MainActivity : FlutterActivity() { super.configureFlutterEngine(flutterEngine) val api = PigeonApiImplementation() - ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api) + ExampleHostApi.setUp( + flutterEngine.dartExecutor.binaryMessenger, + api, + coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())) // #docregion kotlin-init-event val eventListener = EventListener() StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener) diff --git a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt index c6b32713ebf4..c0570e04afb0 100644 --- a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt +++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt @@ -12,6 +12,10 @@ import io.flutter.plugin.common.MessageCodec import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext private fun wrapResult(result: Any?): List { return listOf(result) @@ -120,6 +124,8 @@ interface ExampleHostApi { fun sendMessage(message: MessageData, callback: (Result) -> Unit) + suspend fun sendMessageModernAsync(message: MessageData): Boolean + companion object { /** The codec used by ExampleHostApi. */ val codec: MessageCodec by lazy { MessagesPigeonCodec() } @@ -128,7 +134,8 @@ interface ExampleHostApi { fun setUp( binaryMessenger: BinaryMessenger, api: ExampleHostApi?, - messageChannelSuffix: String = "" + messageChannelSuffix: String = "", + coroutineScope: CoroutineScope ) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" @@ -199,6 +206,30 @@ interface ExampleHostApi { channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val messageArg = args[0] as MessageData + coroutineScope.launch { + val wrapped: List = + try { + listOf(api.sendMessageModernAsync(messageArg)) + } catch (exception: Throwable) { + wrapError(exception) + } + withContext(Dispatchers.Main) { reply.reply(wrapped) } + } + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/packages/pigeon/example/app/ios/Podfile b/packages/pigeon/example/app/ios/Podfile index 01d4aa611bb9..367c048861a4 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, '12.0' +platform :ios, '13.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 121307f17ef8..11d96c7a5009 100644 --- a/packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/example/app/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -146,6 +146,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + C56908DE131A44BB42AA2CE1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -263,6 +264,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + C56908DE131A44BB42AA2CE1 /* [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; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -358,6 +376,7 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -486,6 +505,7 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -508,6 +528,7 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift index 7948edfdd150..ecacd80d5023 100644 --- a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift +++ b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift @@ -25,6 +25,16 @@ private class PigeonApiImplementation: ExampleHostApi { } completion(.success(true)) } + + func sendMessageModernAsync(message: MessageData) async throws -> Bool { + try? await Task.sleep(nanoseconds: 2_000_000_000) + + if message.code == Code.one { + throw PigeonError(code: "code", message: "message", details: "details") + } + + return true + } } // #enddocregion swift-class diff --git a/packages/pigeon/example/app/ios/Runner/Messages.g.swift b/packages/pigeon/example/app/ios/Runner/Messages.g.swift index b42c8a41a1d5..d0ed56bb8c6b 100644 --- a/packages/pigeon/example/app/ios/Runner/Messages.g.swift +++ b/packages/pigeon/example/app/ios/Runner/Messages.g.swift @@ -159,6 +159,7 @@ protocol ExampleHostApi { func getHostLanguage() throws -> String func add(_ a: Int64, to b: Int64) throws -> Int64 func sendMessage(message: MessageData, completion: @escaping (Result) -> Void) + func sendMessageModernAsync(message: MessageData) async throws -> Bool } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -222,6 +223,30 @@ class ExampleHostApiSetup { } else { sendMessageChannel.setMessageHandler(nil) } + let sendMessageModernAsyncChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + sendMessageModernAsyncChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let messageArg = args[0] as! MessageData + Task { + do { + let result = try await api.sendMessageModernAsync(message: messageArg) + await MainActor.run { + reply(wrapResult(result)) + } + } catch { + await MainActor.run { + reply(wrapError(error)) + } + } + } + } + } else { + sendMessageModernAsyncChannel.setMessageHandler(nil) + } } } /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. diff --git a/packages/pigeon/example/app/lib/main.dart b/packages/pigeon/example/app/lib/main.dart index 8c84cd7b906b..2125d4db8b77 100644 --- a/packages/pigeon/example/app/lib/main.dart +++ b/packages/pigeon/example/app/lib/main.dart @@ -86,6 +86,16 @@ class _MyHomePageState extends State { return Future(() => true); } } + + Future sendMessageModernAsync(String messageText) { + final MessageData message = MessageData( + code: Code.two, + data: {'header': 'this is a header'}, + description: 'uri text', + ); + + return _api.sendMessageModernAsync(message); + } // #enddocregion main-dart // #docregion main-dart-event @@ -146,7 +156,36 @@ class _MyHomePageState extends State { }, ) else - const Text('event channels are not supported on this platform') + const Text('event channels are not supported on this platform'), + if (Platform.isAndroid || Platform.isIOS) + ElevatedButton( + onPressed: () async { + final ScaffoldMessengerState scaffoldMessenger = + ScaffoldMessenger.of(context); + scaffoldMessenger.hideCurrentSnackBar(); + + try { + final bool result = await sendMessageModernAsync('test'); + + scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + result.toString(), + ), + ), + ); + } catch (e) { + scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + e.toString(), + ), + ), + ); + } + }, + child: const Text('Send message modern async'), + ) ], ), ), diff --git a/packages/pigeon/example/app/lib/src/messages.g.dart b/packages/pigeon/example/app/lib/src/messages.g.dart index 330809525762..81b9a7f1560e 100644 --- a/packages/pigeon/example/app/lib/src/messages.g.dart +++ b/packages/pigeon/example/app/lib/src/messages.g.dart @@ -203,6 +203,35 @@ class ExampleHostApi { return (pigeonVar_replyList[0] as bool?)!; } } + + Future sendMessageModernAsync(MessageData message) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessageModernAsync$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([message]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } } abstract class MessageFlutterApi { diff --git a/packages/pigeon/example/app/linux/messages.g.cc b/packages/pigeon/example/app/linux/messages.g.cc index 4471586316ea..4767e017a79a 100644 --- a/packages/pigeon/example/app/linux/messages.g.cc +++ b/packages/pigeon/example/app/linux/messages.g.cc @@ -463,6 +463,77 @@ pigeon_example_package_example_host_api_send_message_response_new_error( return self; } +G_DECLARE_FINAL_TYPE( + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse, + pigeon_example_package_example_host_api_send_message_modern_async_response, + PIGEON_EXAMPLE_PACKAGE, EXAMPLE_HOST_API_SEND_MESSAGE_MODERN_ASYNC_RESPONSE, + GObject) + +struct _PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse { + GObject parent_instance; + + FlValue* value; +}; + +G_DEFINE_TYPE( + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse, + pigeon_example_package_example_host_api_send_message_modern_async_response, + G_TYPE_OBJECT) + +static void +pigeon_example_package_example_host_api_send_message_modern_async_response_dispose( + GObject* object) { + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse* self = + PIGEON_EXAMPLE_PACKAGE_EXAMPLE_HOST_API_SEND_MESSAGE_MODERN_ASYNC_RESPONSE( + object); + g_clear_pointer(&self->value, fl_value_unref); + G_OBJECT_CLASS( + pigeon_example_package_example_host_api_send_message_modern_async_response_parent_class) + ->dispose(object); +} + +static void +pigeon_example_package_example_host_api_send_message_modern_async_response_init( + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse* self) {} + +static void +pigeon_example_package_example_host_api_send_message_modern_async_response_class_init( + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponseClass* + klass) { + G_OBJECT_CLASS(klass)->dispose = + pigeon_example_package_example_host_api_send_message_modern_async_response_dispose; +} + +static PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse* +pigeon_example_package_example_host_api_send_message_modern_async_response_new( + gboolean return_value) { + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse* self = + PIGEON_EXAMPLE_PACKAGE_EXAMPLE_HOST_API_SEND_MESSAGE_MODERN_ASYNC_RESPONSE( + g_object_new( + pigeon_example_package_example_host_api_send_message_modern_async_response_get_type(), + nullptr)); + self->value = fl_value_new_list(); + fl_value_append_take(self->value, fl_value_new_bool(return_value)); + return self; +} + +static PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse* +pigeon_example_package_example_host_api_send_message_modern_async_response_new_error( + const gchar* code, const gchar* message, FlValue* details) { + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse* self = + PIGEON_EXAMPLE_PACKAGE_EXAMPLE_HOST_API_SEND_MESSAGE_MODERN_ASYNC_RESPONSE( + g_object_new( + pigeon_example_package_example_host_api_send_message_modern_async_response_get_type(), + nullptr)); + self->value = fl_value_new_list(); + fl_value_append_take(self->value, fl_value_new_string(code)); + fl_value_append_take(self->value, + fl_value_new_string(message != nullptr ? message : "")); + fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) + : fl_value_new_null()); + return self; +} + struct _PigeonExamplePackageExampleHostApi { GObject parent_instance; @@ -582,6 +653,28 @@ static void pigeon_example_package_example_host_api_send_message_cb( self->vtable->send_message(message, handle, self->user_data); } +static void +pigeon_example_package_example_host_api_send_message_modern_async_cb( + FlBasicMessageChannel* channel, FlValue* message_, + FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { + PigeonExamplePackageExampleHostApi* self = + PIGEON_EXAMPLE_PACKAGE_EXAMPLE_HOST_API(user_data); + + if (self->vtable == nullptr || + self->vtable->send_message_modern_async == nullptr) { + return; + } + + FlValue* value0 = fl_value_get_list_value(message_, 0); + PigeonExamplePackageMessageData* message = + PIGEON_EXAMPLE_PACKAGE_MESSAGE_DATA( + fl_value_get_custom_value_object(value0)); + g_autoptr(PigeonExamplePackageExampleHostApiResponseHandle) handle = + pigeon_example_package_example_host_api_response_handle_new( + channel, response_handle); + self->vtable->send_message_modern_async(message, handle, self->user_data); +} + void pigeon_example_package_example_host_api_set_method_handlers( FlBinaryMessenger* messenger, const gchar* suffix, const PigeonExamplePackageExampleHostApiVTable* vtable, gpointer user_data, @@ -623,6 +716,18 @@ void pigeon_example_package_example_host_api_set_method_handlers( send_message_channel, pigeon_example_package_example_host_api_send_message_cb, g_object_ref(api_data), g_object_unref); + g_autofree gchar* send_message_modern_async_channel_name = g_strdup_printf( + "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi." + "sendMessageModernAsync%s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) send_message_modern_async_channel = + fl_basic_message_channel_new(messenger, + send_message_modern_async_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler( + send_message_modern_async_channel, + pigeon_example_package_example_host_api_send_message_modern_async_cb, + g_object_ref(api_data), g_object_unref); } void pigeon_example_package_example_host_api_clear_method_handlers( @@ -656,6 +761,16 @@ void pigeon_example_package_example_host_api_clear_method_handlers( FL_MESSAGE_CODEC(codec)); fl_basic_message_channel_set_message_handler(send_message_channel, nullptr, nullptr, nullptr); + g_autofree gchar* send_message_modern_async_channel_name = g_strdup_printf( + "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi." + "sendMessageModernAsync%s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) send_message_modern_async_channel = + fl_basic_message_channel_new(messenger, + send_message_modern_async_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler( + send_message_modern_async_channel, nullptr, nullptr, nullptr); } void pigeon_example_package_example_host_api_respond_send_message( @@ -688,6 +803,38 @@ void pigeon_example_package_example_host_api_respond_error_send_message( } } +void pigeon_example_package_example_host_api_respond_send_message_modern_async( + PigeonExamplePackageExampleHostApiResponseHandle* response_handle, + gboolean return_value) { + g_autoptr( + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse) response = + pigeon_example_package_example_host_api_send_message_modern_async_response_new( + return_value); + g_autoptr(GError) error = nullptr; + if (!fl_basic_message_channel_respond(response_handle->channel, + response_handle->response_handle, + response->value, &error)) { + g_warning("Failed to send response to %s.%s: %s", "ExampleHostApi", + "sendMessageModernAsync", error->message); + } +} + +void pigeon_example_package_example_host_api_respond_error_send_message_modern_async( + PigeonExamplePackageExampleHostApiResponseHandle* response_handle, + const gchar* code, const gchar* message, FlValue* details) { + g_autoptr( + PigeonExamplePackageExampleHostApiSendMessageModernAsyncResponse) response = + pigeon_example_package_example_host_api_send_message_modern_async_response_new_error( + code, message, details); + g_autoptr(GError) error = nullptr; + if (!fl_basic_message_channel_respond(response_handle->channel, + response_handle->response_handle, + response->value, &error)) { + g_warning("Failed to send response to %s.%s: %s", "ExampleHostApi", + "sendMessageModernAsync", error->message); + } +} + struct _PigeonExamplePackageMessageFlutterApi { GObject parent_instance; diff --git a/packages/pigeon/example/app/linux/messages.g.h b/packages/pigeon/example/app/linux/messages.g.h index ac8dc925fe4b..b880a832565c 100644 --- a/packages/pigeon/example/app/linux/messages.g.h +++ b/packages/pigeon/example/app/linux/messages.g.h @@ -179,6 +179,10 @@ typedef struct { PigeonExamplePackageMessageData* message, PigeonExamplePackageExampleHostApiResponseHandle* response_handle, gpointer user_data); + void (*send_message_modern_async)( + PigeonExamplePackageMessageData* message, + PigeonExamplePackageExampleHostApiResponseHandle* response_handle, + gpointer user_data); } PigeonExamplePackageExampleHostApiVTable; /** @@ -233,6 +237,30 @@ void pigeon_example_package_example_host_api_respond_error_send_message( PigeonExamplePackageExampleHostApiResponseHandle* response_handle, const gchar* code, const gchar* message, FlValue* details); +/** + * pigeon_example_package_example_host_api_respond_send_message_modern_async: + * @response_handle: a #PigeonExamplePackageExampleHostApiResponseHandle. + * @return_value: location to write the value returned by this method. + * + * Responds to ExampleHostApi.sendMessageModernAsync. + */ +void pigeon_example_package_example_host_api_respond_send_message_modern_async( + PigeonExamplePackageExampleHostApiResponseHandle* response_handle, + gboolean return_value); + +/** + * pigeon_example_package_example_host_api_respond_error_send_message_modern_async: + * @response_handle: a #PigeonExamplePackageExampleHostApiResponseHandle. + * @code: error code. + * @message: error message. + * @details: (allow-none): error details or %NULL. + * + * Responds with an error to ExampleHostApi.sendMessageModernAsync. + */ +void pigeon_example_package_example_host_api_respond_error_send_message_modern_async( + PigeonExamplePackageExampleHostApiResponseHandle* response_handle, + const gchar* code, const gchar* message, FlValue* details); + G_DECLARE_FINAL_TYPE( PigeonExamplePackageMessageFlutterApiFlutterMethodResponse, pigeon_example_package_message_flutter_api_flutter_method_response, diff --git a/packages/pigeon/example/app/linux/my_application.cc b/packages/pigeon/example/app/linux/my_application.cc index 812b96a8ceec..97a885fe92cf 100644 --- a/packages/pigeon/example/app/linux/my_application.cc +++ b/packages/pigeon/example/app/linux/my_application.cc @@ -56,10 +56,28 @@ static void handle_send_message( TRUE); } +static void handle_send_message_modern_async( + PigeonExamplePackageMessageData* message, + PigeonExamplePackageExampleHostApiResponseHandle* response_handle, + gpointer user_data) { + PigeonExamplePackageCode code = + pigeon_example_package_message_data_get_code(message); + if (code == PIGEON_EXAMPLE_PACKAGE_CODE_ONE) { + g_autoptr(FlValue) details = fl_value_new_string("details"); + pigeon_example_package_example_host_api_respond_error_send_message_modern_async( + response_handle, "code", "message", details); + return; + } + + pigeon_example_package_example_host_api_respond_send_message_modern_async( + response_handle, TRUE); +} + static PigeonExamplePackageExampleHostApiVTable example_host_api_vtable = { .get_host_language = handle_get_host_language, .add = handle_add, - .send_message = handle_send_message}; + .send_message = handle_send_message, + .send_message_modern_async = handle_send_message_modern_async}; // #enddocregion vtable // #docregion flutter-method-callback diff --git a/packages/pigeon/example/app/macos/Podfile b/packages/pigeon/example/app/macos/Podfile index ae77cc1d4269..66f6172bbb39 100644 --- a/packages/pigeon/example/app/macos/Podfile +++ b/packages/pigeon/example/app/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/pigeon/example/app/macos/Runner.xcodeproj/project.pbxproj b/packages/pigeon/example/app/macos/Runner.xcodeproj/project.pbxproj index ac0c3358e4c3..ed2e8bb5f98d 100644 --- a/packages/pigeon/example/app/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/example/app/macos/Runner.xcodeproj/project.pbxproj @@ -162,7 +162,6 @@ F4D94E001BD74D8D03893EC5 /* Pods-Runner.release.xcconfig */, 4891FB202295F11C6F60A6FC /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -413,6 +412,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -539,6 +539,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -559,6 +560,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/packages/pigeon/example/app/macos/Runner/MainFlutterWindow.swift b/packages/pigeon/example/app/macos/Runner/MainFlutterWindow.swift index 1aa7a4305a35..be88391e4181 100644 --- a/packages/pigeon/example/app/macos/Runner/MainFlutterWindow.swift +++ b/packages/pigeon/example/app/macos/Runner/MainFlutterWindow.swift @@ -24,6 +24,16 @@ private class PigeonApiImplementation: ExampleHostApi { } completion(.success(true)) } + + func sendMessageModernAsync(message: MessageData) async throws -> Bool { + try? await Task.sleep(nanoseconds: 2_000_000_000) + + if message.code == Code.one { + throw PigeonError(code: "code", message: "message", details: "details") + } + + return true + } } class MainFlutterWindow: NSWindow { diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h index 8a51885ec9f1..0ebe561ac9f9 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.h +++ b/packages/pigeon/example/app/macos/Runner/messages.g.h @@ -51,6 +51,9 @@ NSObject *PGNGetMessagesCodec(void); error:(FlutterError *_Nullable *_Nonnull)error; - (void)sendMessageMessage:(PGNMessageData *)message completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +- (void)sendMessageModernAsyncMessage:(PGNMessageData *)message + completion: + (void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @end extern void SetUpPGNExampleHostApi(id binaryMessenger, diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.m b/packages/pigeon/example/app/macos/Runner/messages.g.m index cdf19a2849ba..d2d7da743d68 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.m +++ b/packages/pigeon/example/app/macos/Runner/messages.g.m @@ -229,6 +229,32 @@ void SetUpPGNExampleHostApiWithSuffix(id binaryMessenger [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.pigeon_example_package." + @"ExampleHostApi.sendMessageModernAsync", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:PGNGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(sendMessageModernAsyncMessage:completion:)], + @"PGNExampleHostApi api (%@) doesn't respond to " + @"@selector(sendMessageModernAsyncMessage:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + PGNMessageData *arg_message = GetNullableObjectAtIndex(args, 0); + [api sendMessageModernAsyncMessage:arg_message + completion:^(NSNumber *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } } @interface PGNMessageFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; diff --git a/packages/pigeon/example/app/pigeons/messages.dart b/packages/pigeon/example/app/pigeons/messages.dart index 421e6ce1c9f8..ce89e4db3239 100644 --- a/packages/pigeon/example/app/pigeons/messages.dart +++ b/packages/pigeon/example/app/pigeons/messages.dart @@ -52,6 +52,9 @@ abstract class ExampleHostApi { @async bool sendMessage(MessageData message); + + @modernAsync + bool sendMessageModernAsync(MessageData message); } // #enddocregion host-definitions diff --git a/packages/pigeon/example/app/windows/runner/flutter_window.cpp b/packages/pigeon/example/app/windows/runner/flutter_window.cpp index 5918d55d0ec6..855df3de9cfb 100644 --- a/packages/pigeon/example/app/windows/runner/flutter_window.cpp +++ b/packages/pigeon/example/app/windows/runner/flutter_window.cpp @@ -41,6 +41,15 @@ class PigeonApiImplementation : public ExampleHostApi { } result(true); } + + void SendMessageModernAsync(const MessageData& message, + std::function reply)> result) { + if (message.code() == Code::kOne) { + result(FlutterError("code", "message", "details")); + return; + } + result(true); + } }; // #enddocregion cpp-class diff --git a/packages/pigeon/example/app/windows/runner/messages.g.cpp b/packages/pigeon/example/app/windows/runner/messages.g.cpp index f1643f87edc5..9a007ab72cf6 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.cpp +++ b/packages/pigeon/example/app/windows/runner/messages.g.cpp @@ -271,6 +271,44 @@ void ExampleHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel.SetMessageHandler(nullptr); } } + { + BasicMessageChannel<> channel(binary_messenger, + "dev.flutter.pigeon.pigeon_example_package." + "ExampleHostApi.sendMessageModernAsync" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_message_arg = args.at(0); + if (encodable_message_arg.IsNull()) { + reply(WrapError("message_arg unexpectedly null.")); + return; + } + const auto& message_arg = std::any_cast( + std::get(encodable_message_arg)); + api->SendMessageModernAsync( + message_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } } EncodableValue ExampleHostApi::WrapError(std::string_view error_message) { diff --git a/packages/pigeon/example/app/windows/runner/messages.g.h b/packages/pigeon/example/app/windows/runner/messages.g.h index 7e0e261eb0cd..e7a64e07d56f 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.h +++ b/packages/pigeon/example/app/windows/runner/messages.g.h @@ -124,6 +124,9 @@ class ExampleHostApi { virtual ErrorOr Add(int64_t a, int64_t b) = 0; virtual void SendMessage(const MessageData& message, std::function reply)> result) = 0; + virtual void SendMessageModernAsync( + const MessageData& message, + std::function reply)> result) = 0; // The codec used by ExampleHostApi. static const flutter::StandardMessageCodec& GetCodec(); diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 5e65f31c98ac..4a8645894197 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -23,6 +23,30 @@ enum ApiLocation { flutter, } +/// Enum that represents the type of asynchronous a method is. +enum AsynchronousType { + /// No asynchronous implementation. + none, + + /// Basic callback implementation. + callback, + + /// Modern async implementation. + /// + /// * Swift - async. + /// * Kotlin - suspend. + modern; + + /// Returns true if the [AsynchronousType] is [AsynchronousType.none]. + bool get isNone => this == AsynchronousType.none; + + /// Returns true if the [AsynchronousType] is [AsynchronousType.callback]. + bool get isCallback => this == AsynchronousType.callback; + + /// Returns true if the [AsynchronousType] is [AsynchronousType.modern]. + bool get isModern => this == AsynchronousType.modern; +} + /// Superclass for all AST nodes. class Node {} @@ -35,13 +59,13 @@ class Method extends Node { required this.parameters, required this.location, this.isRequired = true, - this.isAsynchronous = false, this.isStatic = false, this.offset, this.objcSelector = '', this.swiftFunction = '', this.taskQueueType = TaskQueueType.serial, this.documentationComments = const [], + this.asynchronousType = AsynchronousType.none, }); /// The name of the method. @@ -53,9 +77,6 @@ class Method extends Node { /// The parameters passed into the [Method]. List parameters; - /// Whether the receiver of this method is expected to return synchronously or not. - bool isAsynchronous; - /// The offset in the source file where the field appears. int? offset; @@ -87,13 +108,26 @@ class Method extends Node { /// Whether this is a static method of a ProxyApi. bool isStatic; + /// Whether this method is asynchronous and how it should be implemented. + AsynchronousType asynchronousType; + + /// Whether this method is asynchronous. + bool get isAsynchronous => asynchronousType != AsynchronousType.none; + + /// Whether this method is asynchronous with callback. + bool get isCallbackAsynchronous => + asynchronousType == AsynchronousType.callback; + + /// Whether this method is asynchronous with modern Api. + bool get isModernAsynchronous => asynchronousType == AsynchronousType.modern; + @override String toString() { final String objcSelectorStr = objcSelector.isEmpty ? '' : ' objcSelector:$objcSelector'; final String swiftFunctionStr = swiftFunction.isEmpty ? '' : ' swiftFunction:$swiftFunction'; - return '(Method name:$name returnType:$returnType parameters:$parameters isAsynchronous:$isAsynchronous$objcSelectorStr$swiftFunctionStr documentationComments:$documentationComments)'; + return '(Method name:$name returnType:$returnType parameters:$parameters asynchronousType:$asynchronousType$objcSelectorStr$swiftFunctionStr documentationComments:$documentationComments)'; } } diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index f0a185cdcc72..8f58bde91393 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -156,6 +156,13 @@ class KotlinGenerator extends StructuredGenerator { indent.writeln('import io.flutter.plugin.common.StandardMessageCodec'); indent.writeln('import java.io.ByteArrayOutputStream'); indent.writeln('import java.nio.ByteBuffer'); + if (root.apis.any((Api api) => api.methods.any((Method it) => + it.isModernAsynchronous && it.location == ApiLocation.host))) { + indent.writeln('import kotlinx.coroutines.launch'); + indent.writeln('import kotlinx.coroutines.CoroutineScope'); + indent.writeln('import kotlinx.coroutines.Dispatchers'); + indent.writeln('import kotlinx.coroutines.withContext'); + } } @override @@ -338,7 +345,8 @@ class KotlinGenerator extends StructuredGenerator { }) { if (root.apis.any((Api api) => api is AstHostApi && - api.methods.any((Method it) => it.isAsynchronous))) { + api.methods.any((Method it) => + it.isCallbackAsynchronous || it.isModernAsynchronous))) { indent.newln(); } super.writeApis(generatorOptions, root, indent, @@ -621,7 +629,7 @@ if (wrapped == null) { documentationComments: method.documentationComments, returnType: method.returnType, parameters: method.parameters, - isAsynchronous: method.isAsynchronous, + asynchronousType: method.asynchronousType, ); } @@ -637,8 +645,12 @@ if (wrapped == null) { indent.writeln( '/** Sets up an instance of `$apiName` to handle messages through the `binaryMessenger`. */'); indent.writeln('@JvmOverloads'); + final String coroutineScope = + api.methods.any((Method method) => method.isModernAsynchronous) + ? ', coroutineScope: CoroutineScope' + : ''; indent.write( - 'fun setUp(binaryMessenger: BinaryMessenger, api: $apiName?, messageChannelSuffix: String = "") '); + 'fun setUp(binaryMessenger: BinaryMessenger, api: $apiName?, messageChannelSuffix: String = ""$coroutineScope) '); indent.addScoped('{', '}', () { indent.writeln( r'val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""'); @@ -651,7 +663,7 @@ if (wrapped == null) { taskQueueType: method.taskQueueType, parameters: method.parameters, returnType: method.returnType, - isAsynchronous: method.isAsynchronous, + asynchronousType: method.asynchronousType, ); } }); @@ -1163,11 +1175,11 @@ if (wrapped == null) { required List parameters, List documentationComments = const [], int? minApiRequirement, - bool isAsynchronous = false, bool isOpen = false, bool isAbstract = false, String Function(int index, NamedType type) getArgumentName = _getArgumentName, + AsynchronousType asynchronousType = AsynchronousType.none, }) { final List argSignature = []; if (parameters.isNotEmpty) { @@ -1199,19 +1211,20 @@ if (wrapped == null) { final String openKeyword = isOpen ? 'open ' : ''; final String abstractKeyword = isAbstract ? 'abstract ' : ''; + final String suspendKeyword = asynchronousType.isModern ? 'suspend ' : ''; - if (isAsynchronous) { + if (asynchronousType.isCallback) { argSignature.add('callback: (Result<$resultType>) -> Unit'); indent.writeln( '$openKeyword${abstractKeyword}fun $name(${argSignature.join(', ')})', ); } else if (returnType.isVoid) { indent.writeln( - '$openKeyword${abstractKeyword}fun $name(${argSignature.join(', ')})', + '$openKeyword$abstractKeyword${suspendKeyword}fun $name(${argSignature.join(', ')})', ); } else { indent.writeln( - '$openKeyword${abstractKeyword}fun $name(${argSignature.join(', ')}): $returnTypeString', + '$openKeyword$abstractKeyword${suspendKeyword}fun $name(${argSignature.join(', ')}): $returnTypeString', ); } } @@ -1224,9 +1237,9 @@ if (wrapped == null) { required List parameters, required TypeDeclaration returnType, String setHandlerCondition = 'api != null', - bool isAsynchronous = false, String Function(List safeArgNames, {required String apiVarName})? onCreateCall, + AsynchronousType asynchronousType = AsynchronousType.none, }) { indent.write('run '); indent.addScoped('{', '}', () { @@ -1268,7 +1281,7 @@ if (wrapped == null) { ? onCreateCall(methodArguments, apiVarName: 'api') : 'api.$name(${methodArguments.join(', ')})'; - if (isAsynchronous) { + if (asynchronousType.isCallback) { final String resultType = returnType.isVoid ? 'Unit' : _nullSafeKotlinTypeForDartType(returnType); @@ -1288,6 +1301,10 @@ if (wrapped == null) { }); }); } else { + if (asynchronousType.isModern) { + indent.writeln('coroutineScope.launch {'); + indent.inc(); + } indent.writeScoped('val wrapped: List = try {', '}', () { if (returnType.isVoid) { indent.writeln(call); @@ -1300,9 +1317,21 @@ if (wrapped == null) { indent.addScoped('{', '}', () { indent.writeln('wrapError(exception)'); }); + if (asynchronousType.isModern) { + indent.writeln('withContext(Dispatchers.Main) {'); + indent.inc(); + } indent.writeln('reply.reply(wrapped)'); + if (asynchronousType.isModern) { + indent.dec(); + indent.writeln('}'); + } } }); + if (asynchronousType.isModern) { + indent.dec(); + indent.writeln('}'); + } }, addTrailingNewline: false); indent.addScoped(' else {', '}', () { indent.writeln('channel.setMessageHandler(null)'); @@ -1334,7 +1363,7 @@ if (wrapped == null) { returnType: returnType, parameters: parameters, documentationComments: documentationComments, - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, minApiRequirement: minApiRequirement, getArgumentName: _getSafeArgumentName, ); @@ -1627,8 +1656,8 @@ if (wrapped == null) { name: method.name, returnType: method.returnType, documentationComments: method.documentationComments, - isAsynchronous: method.isAsynchronous, isAbstract: true, + asynchronousType: method.asynchronousType, minApiRequirement: _findAndroidHighestApiRequirement( [ if (!method.isStatic) apiAsTypeDeclaration, @@ -1822,7 +1851,7 @@ if (wrapped == null) { channelName: makeChannelName(api, method, dartPackageName), taskQueueType: method.taskQueueType, returnType: method.returnType, - isAsynchronous: method.isAsynchronous, + asynchronousType: method.asynchronousType, parameters: [ if (!method.isStatic) Parameter( diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 4a4501e32508..ad6621532a51 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -41,6 +41,37 @@ class _Asynchronous { const _Asynchronous(); } +/// Metadata to annotate a Api method as asynchronous +const Object async = _Asynchronous(); + +class _ModernAsynchronous { + const _ModernAsynchronous(); +} + +/// Provides a modern asynchronous Api (only Swift and Kotlin). +/// +/// Example: +/// +///```dart +///@HostApi() +///abstract class ExampleHostApi { +/// @modernAsync +/// bool sendMessage(MessageData message); +///} +///``` +/// Swift(iOS 13.0+): +/// +/// ```swift +/// func sendMessage(message: MessageData) async -> Bool +/// ``` +/// +/// Kotlin (`ExampleHostApi.setUp` will require `CoroutineScope`): +/// +/// ```kotlin +/// suspend fun sendMessage(message: MessageData) : Boolean +/// ``` +const Object modernAsync = _ModernAsynchronous(); + class _Attached { const _Attached(); } @@ -49,9 +80,6 @@ class _Static { const _Static(); } -/// Metadata to annotate a Api method as asynchronous -const Object async = _Asynchronous(); - /// Metadata to annotate the field of a ProxyApi as an Attached Field. /// /// Attached fields provide a synchronous [ProxyApi] instance as a field for @@ -2002,7 +2030,9 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final dart_ast.FormalParameterList parameters = node.parameters!; final List arguments = parameters.parameters.map(formalParameterToPigeonParameter).toList(); - final bool isAsynchronous = _hasMetadata(node.metadata, 'async'); + + final AsynchronousType asynchronousType = + _parseAsynchronousType(node.metadata); final bool isStatic = _hasMetadata(node.metadata, 'static'); final String objcSelector = _findMetadata(node.metadata, 'ObjCSelector') ?.arguments @@ -2050,13 +2080,13 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { AstFlutterApi() => ApiLocation.flutter, AstEventChannelApi() => ApiLocation.host, }, - isAsynchronous: isAsynchronous, objcSelector: objcSelector, swiftFunction: swiftFunction, offset: node.offset, taskQueueType: taskQueueType, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), + asynchronousType: asynchronousType, ), ); } else if (_currentClass != null) { @@ -2244,6 +2274,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final TaskQueueType taskQueueType = _stringToEnum(TaskQueueType.values, taskQueueTypeName) ?? TaskQueueType.serial; + final AsynchronousType asynchronousType = + _parseAsynchronousType(node.metadata); // Methods without named return types aren't supported. final dart_ast.TypeAnnotation returnType = type.returnType!; @@ -2262,7 +2294,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { isRequired: type.question == null, isStatic: isStatic, parameters: parameters, - isAsynchronous: _hasMetadata(node.metadata, 'async'), + asynchronousType: asynchronousType, swiftFunction: swiftFunction, offset: node.offset, taskQueueType: taskQueueType, @@ -2303,6 +2335,19 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } +AsynchronousType _parseAsynchronousType( + dart_ast.NodeList metadata) { + if (_hasMetadata(metadata, 'async')) { + return AsynchronousType.callback; + } + + if (_hasMetadata(metadata, 'modernAsync')) { + return AsynchronousType.modern; + } + + return AsynchronousType.none; +} + int? _calculateLineNumberNullable(String contents, int? offset) { return (offset == null) ? null : _calculateLineNumber(contents, offset); } diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index 4962774c9a04..aaa7b3eb29e5 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -602,7 +602,8 @@ if (wrapped == nil) { }) { if (root.apis.any((Api api) => api is AstHostApi && - api.methods.any((Method it) => it.isAsynchronous))) { + api.methods.any((Method it) => + it.isCallbackAsynchronous || it.isModernAsynchronous))) { indent.newln(); } super.writeApis(generatorOptions, root, indent, @@ -639,9 +640,9 @@ if (wrapped == nil) { parameters: func.parameters, returnType: func.returnType, errorTypeName: _getErrorClassName(generatorOptions), - isAsynchronous: true, swiftFunction: func.swiftFunction, getParameterName: _getSafeArgumentName, + asynchronousType: AsynchronousType.callback, )); } }); @@ -711,8 +712,8 @@ if (wrapped == nil) { parameters: method.parameters, returnType: method.returnType, errorTypeName: 'Error', - isAsynchronous: method.isAsynchronous, swiftFunction: method.swiftFunction, + asynchronousType: method.asynchronousType, )); } }); @@ -739,7 +740,7 @@ if (wrapped == nil) { '${makeChannelName(api, method, dartPackageName)}\\(channelSuffix)', parameters: method.parameters, returnType: method.returnType, - isAsynchronous: method.isAsynchronous, + asynchronousType: method.asynchronousType, swiftFunction: method.swiftFunction, documentationComments: method.documentationComments, ); @@ -835,7 +836,6 @@ if (wrapped == nil) { returnType: const TypeDeclaration.voidDeclaration(), swiftFunction: 'method(withIdentifier:)', setHandlerCondition: setHandlerCondition, - isAsynchronous: false, onCreateCall: ( List safeArgNames, { required String apiVarName, @@ -851,7 +851,6 @@ if (wrapped == nil) { returnType: const TypeDeclaration.voidDeclaration(), setHandlerCondition: setHandlerCondition, swiftFunction: null, - isAsynchronous: false, onCreateCall: ( List safeArgNames, { required String apiVarName, @@ -1432,9 +1431,9 @@ private func nilOrValue(_ value: Any?) -> T? { parameters: parameters, returnType: returnType, errorTypeName: _getErrorClassName(generatorOptions), - isAsynchronous: true, swiftFunction: swiftFunction, getParameterName: _getSafeArgumentName, + asynchronousType: AsynchronousType.callback, ); indent.writeScoped('$methodSignature {', '}', () { @@ -1522,12 +1521,12 @@ private func nilOrValue(_ value: Any?) -> T? { required String channelName, required Iterable parameters, required TypeDeclaration returnType, - required bool isAsynchronous, required String? swiftFunction, String setHandlerCondition = 'let api = api', List documentationComments = const [], String Function(List safeArgNames, {required String apiVarName})? onCreateCall, + AsynchronousType asynchronousType = AsynchronousType.none, }) { final _SwiftFunctionComponents components = _SwiftFunctionComponents( name: name, @@ -1575,19 +1574,22 @@ private func nilOrValue(_ value: Any?) -> T? { } }); } - final String tryStatement = isAsynchronous ? '' : 'try '; + final String tryStatement = asynchronousType.isCallback ? '' : 'try '; + final String awaitKeyword = asynchronousType.isModern ? 'await ' : ''; late final String call; if (onCreateCall == null) { // Empty parens are not required when calling a method whose only // argument is a trailing closure. - final String argumentString = methodArgument.isEmpty && isAsynchronous - ? '' - : '(${methodArgument.join(', ')})'; - call = '${tryStatement}api.${components.name}$argumentString'; + final String argumentString = + methodArgument.isEmpty && asynchronousType.isCallback + ? '' + : '(${methodArgument.join(', ')})'; + call = + '$tryStatement${awaitKeyword}api.${components.name}$argumentString'; } else { call = onCreateCall(methodArgument, apiVarName: 'api'); } - if (isAsynchronous) { + if (asynchronousType.isCallback) { final String resultName = returnType.isVoid ? 'nil' : 'res'; final String successVariableInit = returnType.isVoid ? '' : '(let res)'; @@ -1607,21 +1609,44 @@ private func nilOrValue(_ value: Any?) -> T? { }); }); } else { + if (asynchronousType.isModern) { + indent.writeln('Task {'); + indent.inc(); + } + void wrapMainActorRun(void Function() body) { + if (asynchronousType.isModern) { + return indent.writeScoped( + 'await MainActor.run {', + '}', + body, + ); + } + + return body(); + } + indent.write('do '); indent.addScoped('{', '}', () { if (returnType.isVoid) { indent.writeln(call); - indent.writeln('reply(wrapResult(nil))'); + wrapMainActorRun(() => indent.writeln('reply(wrapResult(nil))')); } else { indent.writeln('let result = $call'); - indent.writeln('reply(wrapResult(result))'); + wrapMainActorRun( + () => indent.writeln('reply(wrapResult(result))'), + ); } }, addTrailingNewline: false); indent.addScoped(' catch {', '}', () { - indent.writeln('reply(wrapError(error))'); + wrapMainActorRun(() => indent.writeln('reply(wrapError(error))')); }); } }); + + if (asynchronousType.isModern) { + indent.dec(); + indent.writeln('}'); + } }, addTrailingNewline: false); indent.addScoped(' else {', '}', () { indent.writeln('$varChannelName.setMessageHandler(nil)'); @@ -1986,7 +2011,7 @@ private func nilOrValue(_ value: Any?) -> T? { ...method.parameters, ], returnType: method.returnType, - isAsynchronous: method.isAsynchronous, + asynchronousType: method.asynchronousType, errorTypeName: 'Error', ); indent.writeln(methodSignature); @@ -2123,7 +2148,6 @@ private func nilOrValue(_ value: Any?) -> T? { channelName: channelName, returnType: const TypeDeclaration.voidDeclaration(), swiftFunction: null, - isAsynchronous: false, onCreateCall: ( List methodParameters, { required String apiVarName, @@ -2174,7 +2198,6 @@ private func nilOrValue(_ value: Any?) -> T? { name: field.name, channelName: channelName, swiftFunction: null, - isAsynchronous: false, returnType: const TypeDeclaration.voidDeclaration(), onCreateCall: ( List methodParameters, { @@ -2223,14 +2246,14 @@ private func nilOrValue(_ value: Any?) -> T? { name: method.name, channelName: makeChannelName(api, method, dartPackageName), returnType: method.returnType, - isAsynchronous: method.isAsynchronous, + asynchronousType: method.asynchronousType, swiftFunction: null, onCreateCall: ( List methodParameters, { required String apiVarName, }) { final String tryStatement = - method.isAsynchronous ? '' : 'try '; + method.isCallbackAsynchronous ? '' : 'try '; final List parameters = [ 'pigeonApi: $apiVarName', // Skip the identifier used by the InstanceManager. @@ -2296,7 +2319,7 @@ private func nilOrValue(_ value: Any?) -> T? { Parameter(name: 'pigeonInstance', type: apiAsTypeDeclaration), ], returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, errorTypeName: _getErrorClassName(generatorOptions), ); indent.writeScoped('$methodSignature {', '}', () { @@ -2415,9 +2438,9 @@ private func nilOrValue(_ value: Any?) -> T? { ...method.parameters, ], returnType: method.returnType, - isAsynchronous: true, errorTypeName: _getErrorClassName(generatorOptions), getParameterName: _getSafeArgumentName, + asynchronousType: AsynchronousType.callback, ); indent.write(methodSignature); @@ -2695,10 +2718,10 @@ String _getMethodSignature({ required Iterable parameters, required TypeDeclaration returnType, required String errorTypeName, - bool isAsynchronous = false, String? swiftFunction, String Function(int index, NamedType argument) getParameterName = _getArgumentName, + AsynchronousType asynchronousType = AsynchronousType.none, }) { final _SwiftFunctionComponents components = _SwiftFunctionComponents( name: name, @@ -2721,17 +2744,19 @@ String _getMethodSignature({ return '${label != name ? '$label ' : ''}$name: $type'; }).join(', '); - if (isAsynchronous) { + if (asynchronousType.isCallback) { if (parameters.isEmpty) { return 'func ${components.name}(completion: @escaping (Result<$returnTypeString, $errorTypeName>) -> Void)'; } else { return 'func ${components.name}($parameterSignature, completion: @escaping (Result<$returnTypeString, $errorTypeName>) -> Void)'; } } else { + final String asyncKeyword = asynchronousType.isModern ? 'async ' : ''; + if (returnType.isVoid) { - return 'func ${components.name}($parameterSignature) throws'; + return 'func ${components.name}($parameterSignature) ${asyncKeyword}throws'; } else { - return 'func ${components.name}($parameterSignature) throws -> $returnTypeString'; + return 'func ${components.name}($parameterSignature) ${asyncKeyword}throws -> $returnTypeString'; } } } diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index c88311f4c5be..e4dbeacdb585 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -1855,7 +1855,7 @@ void main() { isNullable: false, associatedClass: emptyClass, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -1984,7 +1984,7 @@ void main() { location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ), Method( name: 'doSomething', @@ -1999,7 +1999,7 @@ void main() { ], returnType: const TypeDeclaration(baseName: 'double', isNullable: false), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ), ]), AstFlutterApi(name: 'FlutterApi', methods: [ @@ -2008,7 +2008,7 @@ void main() { location: ApiLocation.flutter, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ), Method( name: 'doSomething', @@ -2023,7 +2023,7 @@ void main() { ], returnType: const TypeDeclaration(baseName: 'bool', isNullable: false), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ), ]) ], classes: [], enums: []); diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 7a4d44c26177..6f58a77a9634 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -778,7 +778,7 @@ void main() { isNullable: false, associatedClass: emptyClass, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -830,7 +830,7 @@ void main() { name: '') ], returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -885,7 +885,7 @@ void main() { isNullable: false, associatedClass: emptyClass, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -931,7 +931,7 @@ void main() { isNullable: false, associatedClass: emptyClass, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -1260,14 +1260,15 @@ void main() { apis: [ AstHostApi(name: 'Api', methods: [ Method( - name: 'doit', - location: ApiLocation.host, - returnType: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - parameters: [], - isAsynchronous: true) + name: 'doit', + location: ApiLocation.host, + returnType: const TypeDeclaration( + baseName: 'int', + isNullable: true, + ), + parameters: [], + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [], @@ -1321,14 +1322,15 @@ void main() { apis: [ AstFlutterApi(name: 'Api', methods: [ Method( - name: 'doit', - location: ApiLocation.flutter, - returnType: const TypeDeclaration( - baseName: 'int', - isNullable: true, - ), - parameters: [], - isAsynchronous: true) + name: 'doit', + location: ApiLocation.flutter, + returnType: const TypeDeclaration( + baseName: 'int', + isNullable: true, + ), + parameters: [], + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [], @@ -1623,7 +1625,7 @@ name: foobar isNullable: false, associatedClass: emptyClass, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index 49358cf7ddaa..0a1ff39628d7 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -601,7 +601,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -663,7 +663,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -1025,12 +1025,13 @@ void main() { apis: [ AstFlutterApi(name: 'Api', methods: [ Method( - name: 'doit', - location: ApiLocation.flutter, - returnType: - const TypeDeclaration(baseName: 'int', isNullable: false), - parameters: [], - isAsynchronous: true) + name: 'doit', + location: ApiLocation.flutter, + returnType: + const TypeDeclaration(baseName: 'int', isNullable: false), + parameters: [], + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [], @@ -1234,7 +1235,7 @@ void main() { baseName: 'int', isNullable: true, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: []) ]) ], @@ -1530,7 +1531,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index 6043229fd729..287c2fb478aa 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -754,7 +754,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -792,6 +792,69 @@ void main() { expect(code, contains('reply.reply(wrapResult(data))')); }); + test('gen one modern async 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: 'arg') + ], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, + ), + asynchronousType: AsynchronousType.modern, + ) + ]) + ], 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: []); + final StringBuffer sink = StringBuffer(); + const KotlinOptions kotlinOptions = KotlinOptions(); + const KotlinGenerator generator = KotlinGenerator(); + generator.generate( + kotlinOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, contains('interface Api')); + expect( + code, + contains('suspend fun doSomething(arg: Input): Output'), + ); + expect(code, contains('coroutineScope.launch {')); + expect(code, contains('coroutineScope: CoroutineScope')); + expect(code, contains('api.doSomething(argArg)')); + expect(code, contains('withContext(Dispatchers.Main) {')); + expect(code, contains('reply.reply(wrapped)')); + }); + test('gen one async Flutter Api', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ @@ -813,7 +876,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -1239,7 +1302,7 @@ void main() { baseName: 'int', isNullable: true, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: []) ]) ], @@ -1487,7 +1550,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index 728bb50ac9c7..876606ebc57d 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -1330,19 +1330,20 @@ void main() { 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: 'input') - ], - returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + associatedClass: emptyClass, + isNullable: false, + ), + name: 'input') + ], + returnType: const TypeDeclaration.voidDeclaration(), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [ Class(name: 'Input', fields: [ @@ -1381,23 +1382,24 @@ void main() { 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: 'input') - ], - returnType: TypeDeclaration( - baseName: 'Output', - associatedClass: emptyClass, - isNullable: false, - ), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + associatedClass: emptyClass, + isNullable: false, + ), + name: 'input') + ], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, + ), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [ Class(name: 'Input', fields: [ @@ -1436,15 +1438,16 @@ void main() { final Root root = Root(apis: [ AstHostApi(name: 'Api', methods: [ Method( - name: 'doSomething', - location: ApiLocation.host, - parameters: [], - returnType: TypeDeclaration( - baseName: 'Output', - associatedClass: emptyClass, - isNullable: false, - ), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, + ), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [ Class(name: 'Output', fields: [ @@ -1478,11 +1481,12 @@ void main() { final Root root = Root(apis: [ AstHostApi(name: 'Api', methods: [ Method( - name: 'doSomething', - location: ApiLocation.host, - parameters: [], - returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [], + returnType: const TypeDeclaration.voidDeclaration(), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); @@ -1510,23 +1514,24 @@ void main() { 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, - ), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + associatedClass: emptyClass, + isNullable: false, + ), + name: '') + ], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, + ), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [ Class(name: 'Input', fields: [ @@ -1565,19 +1570,20 @@ void main() { 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: 'foo') - ], - returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + associatedClass: emptyClass, + isNullable: false, + ), + name: 'foo') + ], + returnType: const TypeDeclaration.voidDeclaration(), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [ Class(name: 'Input', fields: [ @@ -1616,11 +1622,12 @@ void main() { final Root root = Root(apis: [ AstHostApi(name: 'Api', methods: [ Method( - name: 'doSomething', - location: ApiLocation.host, - parameters: [], - returnType: const TypeDeclaration.voidDeclaration(), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [], + returnType: const TypeDeclaration.voidDeclaration(), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [], enums: []); final StringBuffer sink = StringBuffer(); @@ -1648,15 +1655,16 @@ void main() { final Root root = Root(apis: [ AstHostApi(name: 'Api', methods: [ Method( - name: 'doSomething', - location: ApiLocation.host, - parameters: [], - returnType: TypeDeclaration( - baseName: 'Output', - associatedClass: emptyClass, - isNullable: false, - ), - isAsynchronous: true) + name: 'doSomething', + location: ApiLocation.host, + parameters: [], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, + ), + asynchronousType: AsynchronousType.callback, + ) ]) ], classes: [ Class(name: 'Output', fields: [ @@ -2150,7 +2158,7 @@ void main() { const TypeDeclaration(isNullable: false, baseName: 'int')), ], returnType: const TypeDeclaration(baseName: 'int', isNullable: false), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [], enums: []); @@ -2798,7 +2806,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -2897,7 +2905,7 @@ void main() { Method( name: 'doSomething', location: ApiLocation.flutter, - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: [], returnType: TypeDeclaration( baseName: 'Enum1', @@ -2940,7 +2948,7 @@ void main() { Method( name: 'doSomething', location: ApiLocation.flutter, - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: [], returnType: TypeDeclaration( baseName: 'Enum1', @@ -2988,7 +2996,7 @@ void main() { Method( name: 'doSomething', location: ApiLocation.host, - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: [Parameter(name: 'value', type: enumType)], returnType: enumType, ) @@ -3032,7 +3040,7 @@ void main() { Method( name: 'doSomething', location: ApiLocation.host, - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: [Parameter(name: 'value', type: enumType)], returnType: enumType, ) diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index 6ad36f934ecc..5633a2c5f3a5 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -611,7 +611,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -648,6 +648,68 @@ void main() { expect(code, isNot(contains('if ('))); }); + test('gen one modern async 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: 'arg') + ], + returnType: TypeDeclaration( + baseName: 'Output', + associatedClass: emptyClass, + isNullable: false, + ), + asynchronousType: AsynchronousType.modern, + ) + ]) + ], 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: []); + final StringBuffer sink = StringBuffer(); + const SwiftOptions swiftOptions = SwiftOptions(); + const SwiftGenerator generator = SwiftGenerator(); + generator.generate( + swiftOptions, + root, + sink, + dartPackageName: DEFAULT_PACKAGE_NAME, + ); + final String code = sink.toString(); + expect(code, contains('protocol Api')); + expect( + code, + contains('func doSomething(arg: Input) async throws -> Output'), + ); + expect(code, contains('try await api.doSomething(arg: argArg)')); + expect(code, contains('Task')); + expect(code, contains('await MainActor.run {')); + expect(code, contains('reply(wrapResult(result))')); + }); + test('gen one async Flutter Api', () { final Root root = Root(apis: [ AstFlutterApi(name: 'Api', methods: [ @@ -668,7 +730,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ @@ -1094,7 +1156,7 @@ void main() { baseName: 'int', isNullable: true, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, parameters: []) ]) ], @@ -1339,7 +1401,7 @@ void main() { associatedClass: emptyClass, isNullable: false, ), - isAsynchronous: true, + asynchronousType: AsynchronousType.callback, ) ]) ], classes: [ diff --git a/script/tool/lib/src/common/package_looping_command.dart b/script/tool/lib/src/common/package_looping_command.dart index 9dc32279e929..b9c7a87c067b 100644 --- a/script/tool/lib/src/common/package_looping_command.dart +++ b/script/tool/lib/src/common/package_looping_command.dart @@ -340,7 +340,7 @@ abstract class PackageLoopingCommand extends PackageCommand { if (minFlutterVersion != null) { final Pubspec pubspec = package.parsePubspec(); final VersionConstraint? flutterConstraint = - pubspec.environment?['flutter']; + pubspec.environment['flutter']; if (flutterConstraint != null && !flutterConstraint.allows(minFlutterVersion)) { return PackageResult.skip( @@ -350,7 +350,7 @@ abstract class PackageLoopingCommand extends PackageCommand { if (minDartVersion != null) { final Pubspec pubspec = package.parsePubspec(); - final VersionConstraint? dartConstraint = pubspec.environment?['sdk']; + final VersionConstraint? dartConstraint = pubspec.environment['sdk']; if (dartConstraint != null && !dartConstraint.allows(minDartVersion)) { return PackageResult.skip('Does not support Dart $minDartVersion'); } diff --git a/script/tool/lib/src/create_all_packages_app_command.dart b/script/tool/lib/src/create_all_packages_app_command.dart index ab6b5637dd7b..812c32133b25 100644 --- a/script/tool/lib/src/create_all_packages_app_command.dart +++ b/script/tool/lib/src/create_all_packages_app_command.dart @@ -277,7 +277,7 @@ dependencies {} final Pubspec originalPubspec = app.parsePubspec(); const String dartSdkKey = 'sdk'; final VersionConstraint dartSdkConstraint = - originalPubspec.environment?[dartSdkKey] ?? + originalPubspec.environment[dartSdkKey] ?? VersionConstraint.compatibleWith( Version.parse('3.0.0'), ); @@ -342,7 +342,7 @@ publish_to: none version: ${pubspec.version} -environment:${_pubspecMapString(pubspec.environment!)} +environment:${_pubspecMapString(pubspec.environment)} dependencies:${_pubspecMapString(pubspec.dependencies)} diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart index 66ac470d8235..d8fafb1b2b62 100644 --- a/script/tool/lib/src/pubspec_check_command.dart +++ b/script/tool/lib/src/pubspec_check_command.dart @@ -488,9 +488,9 @@ class PubspecCheckCommand extends PackageLoopingCommand { } final Version? dartConstraintMin = - _minimumForConstraint(pubspec.environment?['sdk']); + _minimumForConstraint(pubspec.environment['sdk']); final Version? flutterConstraintMin = - _minimumForConstraint(pubspec.environment?['flutter']); + _minimumForConstraint(pubspec.environment['flutter']); // Validate the Flutter constraint, if any. if (flutterConstraintMin != null && minMinFlutterVersion != null) { diff --git a/script/tool/lib/src/update_min_sdk_command.dart b/script/tool/lib/src/update_min_sdk_command.dart index 429036a47ef9..603886ce94ad 100644 --- a/script/tool/lib/src/update_min_sdk_command.dart +++ b/script/tool/lib/src/update_min_sdk_command.dart @@ -87,7 +87,7 @@ class UpdateMinSdkCommand extends PackageLoopingCommand { /// Returns the given "environment" section's [key] constraint as a range, /// if the key is present and has a range. VersionRange? _sdkRange(Pubspec pubspec, String key) { - final VersionConstraint? constraint = pubspec.environment?[key]; + final VersionConstraint? constraint = pubspec.environment[key]; if (constraint is VersionRange) { return constraint; } diff --git a/script/tool/test/create_all_packages_app_command_test.dart b/script/tool/test/create_all_packages_app_command_test.dart index bed615f12b6f..b65371a1971a 100644 --- a/script/tool/test/create_all_packages_app_command_test.dart +++ b/script/tool/test/create_all_packages_app_command_test.dart @@ -356,7 +356,7 @@ android { final Pubspec generatedPubspec = command.app.parsePubspec(); const String dartSdkKey = 'sdk'; - expect(generatedPubspec.environment?[dartSdkKey].toString(), + expect(generatedPubspec.environment[dartSdkKey].toString(), existingSdkConstraint); }); diff --git a/script/tool/test/update_min_sdk_command_test.dart b/script/tool/test/update_min_sdk_command_test.dart index 52d69ccb7bd0..d9bc93f23105 100644 --- a/script/tool/test/update_min_sdk_command_test.dart +++ b/script/tool/test/update_min_sdk_command_test.dart @@ -50,7 +50,7 @@ void main() { ]); final String dartVersion = - package.parsePubspec().environment?['sdk'].toString() ?? ''; + package.parsePubspec().environment['sdk'].toString(); expect(dartVersion, '^3.1.0'); }); @@ -65,7 +65,7 @@ void main() { ]); final String dartVersion = - package.parsePubspec().environment?['sdk'].toString() ?? ''; + package.parsePubspec().environment['sdk'].toString(); expect(dartVersion, '^3.1.0'); }); @@ -80,7 +80,7 @@ void main() { ]); final String dartVersion = - package.parsePubspec().environment?['sdk'].toString() ?? ''; + package.parsePubspec().environment['sdk'].toString(); expect(dartVersion, '^3.2.0'); }); @@ -98,9 +98,9 @@ void main() { ]); final String dartVersion = - package.parsePubspec().environment?['sdk'].toString() ?? ''; + package.parsePubspec().environment['sdk'].toString(); final String flutterVersion = - package.parsePubspec().environment?['flutter'].toString() ?? ''; + package.parsePubspec().environment['flutter'].toString(); expect(dartVersion, '^3.1.0'); expect(flutterVersion, '>=3.13.0'); }); @@ -119,9 +119,9 @@ void main() { ]); final String dartVersion = - package.parsePubspec().environment?['sdk'].toString() ?? ''; + package.parsePubspec().environment['sdk'].toString(); final String flutterVersion = - package.parsePubspec().environment?['flutter'].toString() ?? ''; + package.parsePubspec().environment['flutter'].toString(); expect(dartVersion, '^3.2.0'); expect(flutterVersion, '>=3.16.0'); });