From 9d3519532bac560b78660238f501c43894a29678 Mon Sep 17 00:00:00 2001 From: Fedor Blagodyr Date: Mon, 23 Dec 2024 13:33:09 +0300 Subject: [PATCH] issue(123867): added modern asynchronous api for Swift and Kotlin --- packages/pigeon/CHANGELOG.md | 1 + .../java/io/flutter/plugins/Messages.java | 130 ++++++------ .../pigeon_example_app/MainActivity.kt | 16 +- .../flutter/pigeon_example_app/Messages.g.kt | 175 ++++++++-------- 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 | 68 +++--- packages/pigeon/example/app/lib/main.dart | 41 +++- .../example/app/lib/src/messages.g.dart | 88 ++++---- .../pigeon/example/app/linux/messages.g.cc | 149 ++++++++++++- .../pigeon/example/app/linux/messages.g.h | 30 ++- .../example/app/macos/Runner/messages.g.h | 5 +- .../example/app/macos/Runner/messages.g.m | 28 ++- .../pigeon/example/app/pigeons/messages.dart | 3 + .../example/app/windows/runner/messages.g.cpp | 40 +++- .../example/app/windows/runner/messages.g.h | 5 +- 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 ++++++- 27 files changed, 1047 insertions(+), 416 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..38d2ff06eebf 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 @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import static java.lang.annotation.ElementType.METHOD; @@ -19,7 +19,9 @@ import java.lang.annotation.Target; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -37,7 +39,8 @@ public static class FlutterError extends RuntimeException { /** The error details. Must be a datatype supported by the api codec. */ public final Object details; - public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) + { super(message); this.code = code; this.details = details; @@ -56,15 +59,14 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { errorList.add(exception.toString()); errorList.add(exception.getClass().getSimpleName()); errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); } return errorList; } @NonNull protected static FlutterError createConnectionError(@NonNull String channelName) { - return new FlutterError( - "channel-error", "Unable to establish connection on channel: " + channelName + ".", ""); + return new FlutterError("channel-error", "Unable to establish connection on channel: " + channelName + ".", ""); } @Target(METHOD) @@ -135,17 +137,10 @@ public void setData(@NonNull Map setterArg) { @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } MessageData that = (MessageData) o; - return Objects.equals(name, that.name) - && Objects.equals(description, that.description) - && code.equals(that.code) - && data.equals(that.data); + return Objects.equals(name, that.name) && Objects.equals(description, that.description) && code.equals(that.code) && data.equals(that.data); } @Override @@ -229,11 +224,10 @@ private PigeonCodec() {} @Override protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { - case (byte) 129: - { - Object value = readValue(buffer); - return value == null ? null : Code.values()[((Long) value).intValue()]; - } + case (byte) 129: { + Object value = readValue(buffer); + return value == null ? null : Code.values()[((Long) value).intValue()]; + } case (byte) 130: return MessageData.fromList((ArrayList) readValue(buffer)); default: @@ -255,6 +249,7 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } } + /** Asynchronous error handling return type for non-nullable API method returns. */ public interface Result { /** Success case callback method for handling returns. */ @@ -282,35 +277,30 @@ public interface VoidResult { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface ExampleHostApi { - @NonNull + @NonNull String getHostLanguage(); - @NonNull + @NonNull Long add(@NonNull Long a, @NonNull Long b); 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; } - /** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ + /**Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable ExampleHostApi api) { setUp(binaryMessenger, "", api); } - - static void setUp( - @NonNull BinaryMessenger binaryMessenger, - @NonNull String messageChannelSuffix, - @Nullable ExampleHostApi api) { + static void setUp(@NonNull BinaryMessenger binaryMessenger, @NonNull String messageChannelSuffix, @Nullable ExampleHostApi api) { messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage" - + messageChannelSuffix, - getCodec()); + binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -318,7 +308,8 @@ static void setUp( try { String output = api.getHostLanguage(); wrapped.add(0, output); - } catch (Throwable exception) { + } + catch (Throwable exception) { wrapped = wrapError(exception); } reply.reply(wrapped); @@ -330,10 +321,7 @@ static void setUp( { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add" - + messageChannelSuffix, - getCodec()); + binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -344,7 +332,8 @@ static void setUp( try { Long output = api.add(aArg, bArg); wrapped.add(0, output); - } catch (Throwable exception) { + } + catch (Throwable exception) { wrapped = wrapError(exception); } reply.reply(wrapped); @@ -356,10 +345,7 @@ static void setUp( { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage" - + messageChannelSuffix, - getCodec()); + binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -385,6 +371,35 @@ 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. */ @@ -395,47 +410,40 @@ public static class MessageFlutterApi { public MessageFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { this(argBinaryMessenger, ""); } - - public MessageFlutterApi( - @NonNull BinaryMessenger argBinaryMessenger, @NonNull String messageChannelSuffix) { + public MessageFlutterApi(@NonNull BinaryMessenger argBinaryMessenger, @NonNull String messageChannelSuffix) { this.binaryMessenger = argBinaryMessenger; this.messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; } - /** Public interface for sending reply. The codec used by MessageFlutterApi. */ + /** + * Public interface for sending reply. + * The codec used by MessageFlutterApi. + */ static @NonNull MessageCodec getCodec() { return PigeonCodec.INSTANCE; } - public void flutterMethod(@Nullable String aStringArg, @NonNull Result result) { - final String channelName = - "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod" - + messageChannelSuffix; + final String channelName = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod" + messageChannelSuffix; BasicMessageChannel channel = - new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); + new BasicMessageChannel<>( + binaryMessenger, channelName, getCodec()); channel.send( new ArrayList<>(Collections.singletonList(aStringArg)), channelReply -> { if (channelReply instanceof List) { List listReply = (List) channelReply; if (listReply.size() > 1) { - result.error( - new FlutterError( - (String) listReply.get(0), (String) listReply.get(1), listReply.get(2))); + result.error(new FlutterError((String) listReply.get(0), (String) listReply.get(1), listReply.get(2))); } else if (listReply.get(0) == null) { - result.error( - new FlutterError( - "null-error", - "Flutter api returned null value for non-null return value.", - "")); + result.error(new FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")); } else { @SuppressWarnings("ConstantConditions") String output = (String) listReply.get(0); result.success(output); } - } else { + } else { result.error(createConnectionError(channelName)); - } + } }); } } 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..a8fc1237dfa9 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,7 @@ 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..0e8c6da0b023 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 @@ -1,17 +1,24 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + import android.util.Log import io.flutter.plugin.common.BasicMessageChannel import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer +import kotlinx.coroutines.launch +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext private fun wrapResult(result: Any?): List { return listOf(result) @@ -19,31 +26,33 @@ private fun wrapResult(result: Any?): List { private fun wrapError(exception: Throwable): List { return if (exception is FlutterError) { - listOf(exception.code, exception.message, exception.details) + listOf( + exception.code, + exception.message, + exception.details + ) } else { listOf( - exception.javaClass.simpleName, - exception.toString(), - "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) } } private fun createConnectionError(channelName: String): FlutterError { - return FlutterError( - "channel-error", "Unable to establish connection on channel: '$channelName'.", "") -} + return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "")} /** * 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 FlutterError( - val code: String, - override val message: String? = null, - val details: Any? = null +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null ) : Throwable() enum class Code(val raw: Int) { @@ -58,12 +67,13 @@ enum class Code(val raw: Int) { } /** Generated class from Pigeon that represents data sent in messages. */ -data class MessageData( - val name: String? = null, - val description: String? = null, - val code: Code, - val data: Map -) { +data class MessageData ( + val name: String? = null, + val description: String? = null, + val code: Code, + val data: Map +) + { companion object { fun fromList(pigeonVar_list: List): MessageData { val name = pigeonVar_list[0] as String? @@ -73,31 +83,32 @@ data class MessageData( return MessageData(name, description, code, data) } } - fun toList(): List { return listOf( - name, - description, - code, - data, + name, + description, + code, + data, ) } } - private open class MessagesPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { 129.toByte() -> { - return (readValue(buffer) as Long?)?.let { Code.ofRaw(it.toInt()) } + return (readValue(buffer) as Long?)?.let { + Code.ofRaw(it.toInt()) + } } 130.toByte() -> { - return (readValue(buffer) as? List)?.let { MessageData.fromList(it) } + return (readValue(buffer) as? List)?.let { + MessageData.fromList(it) + } } else -> super.readValueOfType(type, buffer) } } - - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { when (value) { is Code -> { stream.write(129) @@ -112,40 +123,32 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { } } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface ExampleHostApi { fun getHostLanguage(): String - fun add(a: Long, b: Long): Long - 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() } + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } /** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ @JvmOverloads - fun setUp( - binaryMessenger: BinaryMessenger, - api: ExampleHostApi?, - messageChannelSuffix: String = "" - ) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + fun setUp(binaryMessenger: BinaryMessenger, api: ExampleHostApi?, messageChannelSuffix: String = "", coroutineScope: CoroutineScope) { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getHostLanguage()) - } catch (exception: Throwable) { - wrapError(exception) - } + val wrapped: List = try { + listOf(api.getHostLanguage()) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -153,22 +156,17 @@ interface ExampleHostApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val aArg = args[0] as Long val bArg = args[1] as Long - val wrapped: List = - try { - listOf(api.add(aArg, bArg)) - } catch (exception: Throwable) { - wrapError(exception) - } + val wrapped: List = try { + listOf(api.add(aArg, bArg)) + } catch (exception: Throwable) { + wrapError(exception) + } reply.reply(wrapped) } } else { @@ -176,11 +174,7 @@ interface ExampleHostApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List @@ -199,43 +193,56 @@ 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) + } + } } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ -class MessageFlutterApi( - private val binaryMessenger: BinaryMessenger, - private val messageChannelSuffix: String = "" -) { +class MessageFlutterApi(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { companion object { /** The codec used by MessageFlutterApi. */ - val codec: MessageCodec by lazy { MessagesPigeonCodec() } + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } } - - fun flutterMethod(aStringArg: String?, callback: (Result) -> Unit) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" - val channelName = - "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod$separatedMessageChannelSuffix" + fun flutterMethod(aStringArg: String?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) channel.send(listOf(aStringArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) } else if (it[0] == null) { - callback( - Result.failure( - FlutterError( - "null-error", - "Flutter api returned null value for non-null return value.", - ""))) + callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", ""))) } else { val output = it[0] as String callback(Result.success(output)) } } else { callback(Result.failure(createConnectionError(channelName))) - } + } } } } 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..5749fa56d3bc 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..9e1ba8031571 100644 --- a/packages/pigeon/example/app/ios/Runner/Messages.g.swift +++ b/packages/pigeon/example/app/ios/Runner/Messages.g.swift @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -29,7 +29,7 @@ final class PigeonError: Error { var localizedDescription: String { return "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } + } } private func wrapResult(_ result: Any?) -> [Any?] { @@ -59,9 +59,7 @@ private func wrapError(_ error: Any) -> [Any?] { } private func createConnectionError(withChannelName channelName: String) -> PigeonError { - return PigeonError( - code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", - details: "") + return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") } private func isNullish(_ value: Any?) -> Bool { @@ -85,6 +83,7 @@ struct MessageData { var code: Code var data: [String: String] + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> MessageData? { let name: String? = nilOrValue(pigeonVar_list[0]) @@ -154,25 +153,22 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) } + /// Generated protocol from Pigeon that represents a handler of messages from Flutter. 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`. class ExampleHostApiSetup { static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } /// Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. - static func setUp( - binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?, messageChannelSuffix: String = "" - ) { + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?, messageChannelSuffix: String = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let getHostLanguageChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let getHostLanguageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getHostLanguageChannel.setMessageHandler { _, reply in do { @@ -185,9 +181,7 @@ class ExampleHostApiSetup { } else { getHostLanguageChannel.setMessageHandler(nil) } - let addChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let addChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { addChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -203,9 +197,7 @@ class ExampleHostApiSetup { } else { addChannel.setMessageHandler(nil) } - let sendMessageChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let sendMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { sendMessageChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -222,12 +214,32 @@ 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) + DispatchQueue.main.async { + reply(wrapResult(result)) + } + } catch { + DispatchQueue.main.async { + reply(wrapError(error)) + } + } + } + } + } else { + sendMessageModernAsyncChannel.setMessageHandler(nil) + } } } /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol MessageFlutterApiProtocol { - func flutterMethod( - aString aStringArg: String?, completion: @escaping (Result) -> Void) + func flutterMethod(aString aStringArg: String?, completion: @escaping (Result) -> Void) } class MessageFlutterApi: MessageFlutterApiProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -239,13 +251,9 @@ class MessageFlutterApi: MessageFlutterApiProtocol { var codec: MessagesPigeonCodec { return MessagesPigeonCodec.shared } - func flutterMethod( - aString aStringArg: String?, completion: @escaping (Result) -> Void - ) { - let channelName: String = - "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod\(messageChannelSuffix)" - let channel = FlutterBasicMessageChannel( - name: channelName, binaryMessenger: binaryMessenger, codec: codec) + func flutterMethod(aString aStringArg: String?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage([aStringArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) @@ -257,11 +265,7 @@ class MessageFlutterApi: MessageFlutterApiProtocol { let details: String? = nilOrValue(listResponse[2]) completion(.failure(PigeonError(code: code, message: message, details: details))) } else if listResponse[0] == nil { - completion( - .failure( - PigeonError( - code: "null-error", - message: "Flutter api returned null value for non-null return value.", details: ""))) + completion(.failure(PigeonError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: ""))) } else { let result = listResponse[0] as! String completion(.success(result)) 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..9929e71683e7 100644 --- a/packages/pigeon/example/app/lib/src/messages.g.dart +++ b/packages/pigeon/example/app/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), 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 @@ -18,8 +18,7 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -70,6 +69,7 @@ class MessageData { } } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -77,10 +77,10 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is Code) { + } else if (value is Code) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is MessageData) { + } else if (value is MessageData) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else { @@ -91,10 +91,10 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : Code.values[value]; - case 130: + case 130: return MessageData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -106,11 +106,9 @@ class ExampleHostApi { /// Constructor for [ExampleHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - ExampleHostApi( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + ExampleHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -118,10 +116,8 @@ class ExampleHostApi { final String pigeonVar_messageChannelSuffix; Future getHostLanguage() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.getHostLanguage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -147,10 +143,8 @@ class ExampleHostApi { } Future add(int a, int b) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.add$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -176,10 +170,35 @@ class ExampleHostApi { } Future sendMessage(MessageData message) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_example_package.ExampleHostApi.sendMessage$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?)!; + } + } + + 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, @@ -210,26 +229,18 @@ abstract class MessageFlutterApi { String flutterMethod(String? aString); - static void setUp( - MessageFlutterApi? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(MessageFlutterApi? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod was null.'); + 'Argument for dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod was null.'); final List args = (message as List?)!; final String? arg_aString = (args[0] as String?); try { @@ -237,9 +248,8 @@ abstract class MessageFlutterApi { return wrapResponse(result: output); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } diff --git a/packages/pigeon/example/app/linux/messages.g.cc b/packages/pigeon/example/app/linux/messages.g.cc index 4471586316ea..3f275348df11 100644 --- a/packages/pigeon/example/app/linux/messages.g.cc +++ b/packages/pigeon/example/app/linux/messages.g.cc @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #include "messages.g.h" @@ -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..b13fcf821d19 100644 --- a/packages/pigeon/example/app/linux/messages.g.h +++ b/packages/pigeon/example/app/linux/messages.g.h @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_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/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h index 8a51885ec9f1..26c889af11d7 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.h +++ b/packages/pigeon/example/app/macos/Runner/messages.g.h @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -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..6829c765565a 100644 --- a/packages/pigeon/example/app/macos/Runner/messages.g.m +++ b/packages/pigeon/example/app/macos/Runner/messages.g.m @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" @@ -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/messages.g.cpp b/packages/pigeon/example/app/windows/runner/messages.g.cpp index f1643f87edc5..9cd7cc7b037e 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.cpp +++ b/packages/pigeon/example/app/windows/runner/messages.g.cpp @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -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..e1d93b66d4ce 100644 --- a/packages/pigeon/example/app/windows/runner/messages.g.h +++ b/packages/pigeon/example/app/windows/runner/messages.g.h @@ -1,7 +1,7 @@ // 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. +// Autogenerated from Pigeon (v22.7.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_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..075134dbd1d0 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 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..029ac2d66e50 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 wrapDispatchMain(void Function() body) { + if (asynchronousType.isModern) { + return indent.writeScoped( + 'DispatchQueue.main.async {', + '}', + body, + ); + } + + return body(); + } + indent.write('do '); indent.addScoped('{', '}', () { if (returnType.isVoid) { indent.writeln(call); - indent.writeln('reply(wrapResult(nil))'); + wrapDispatchMain(() => indent.writeln('reply(wrapResult(nil))')); } else { indent.writeln('let result = $call'); - indent.writeln('reply(wrapResult(result))'); + wrapDispatchMain( + () => indent.writeln('reply(wrapResult(result))'), + ); } }, addTrailingNewline: false); indent.addScoped(' catch {', '}', () { - indent.writeln('reply(wrapError(error))'); + wrapDispatchMain(() => 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..27be1433f47b 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('DispatchQueue.main.async {')); + 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: [