diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 861b58d6e19e..addd59f7268d 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 10.1.1 + +* Updates README to better reflect modern usage. + ## 10.1.0 * [objc] Adds macOS support to facilitate code sharing with existing iOS plugins. diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index 525b6262c31b..c7810ae210d8 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -1,33 +1,107 @@ # Pigeon Pigeon is a code generator tool to make communication between Flutter and the -host platform type-safe, easier and faster. +host platform type-safe, easier, and faster. -## Supported Platforms +Pigeon removes the necessity to manage strings across multiple platforms and languages. +It also improves efficiency over common method channel patterns. Most importantly though, +it removes the need to write custom platform channel code, since pigeon generates it for you. -Currently Pigeon supports generating: +For usage examples, see the [Example README](./example/README.md). + +## Features + +### Supported Platforms + +Currently pigeon supports generating: * Kotlin and Java code for Android * Swift and Objective-C code for iOS and macOS * C++ code for Windows -## Runtime Requirements +### Supported Datatypes + +Pigeon uses the `StandardMessageCodec` so it supports +[[any datatype platform channels support](https://flutter.dev/docs/development/platform-integration/platform-channels#codec)]. + +Custom classes and nested datatypes are also supported. + +#### Enums + +Pigeon currently supports enum generation in class fields only. +See issue: [87307](https://github.com/flutter/flutter/issues/87307). + +### Synchronous and Asynchronous methods + +While all calls across platform channel APIs (such as pigeon methods) are asynchronous, +pigeon methods can be written on the native side as synchronous methods, +to make it simpler to always reply exactly once. -Pigeon generates all the code that is needed to communicate between Flutter and -the host platform, there is no extra runtime requirement. A plugin author -doesn't need to worry about conflicting versions of Pigeon. +If asynchronous methods are needed, the `@async` annotation can be used. This will require +results or errors to be returned via a provided callback. [Example](./example/README.md#HostApi_Example). + +### Error Handling + +#### Kotlin, Java and Swift + +All Host API exceptions are translated into Flutter `PlatformException`. +* For synchronous methods, thrown exceptions will be caught and translated. +* For asynchronous methods, there is no default exception handling; errors +should be returned via the provided callback. + +To pass custom details into `PlatformException` for error handling, +use `FlutterError` in your Host API. [Example](./example/README.md#HostApi_Example). + +To use `FlutterError` in Swift you must first extend a standard error. +[Example](./example/README.md#AppDelegate.swift). + +#### Objective-C and C++ + +Host API errors can be sent using the provided `FlutterError` class (translated into `PlatformException`). + +For synchronous methods: +* Objective-C - Set the `error` argument to a `FlutterError` reference. +* C++ - Return a `FlutterError`. + +For async methods: +* Return a `FlutterError` through the provided callback. + + +### Task Queue + +When targeting a Flutter version that supports the +[TaskQueue API](https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-kotlin-tab#channels-and-platform-threading) +the threading model for handling HostApi methods can be selected with the +`TaskQueue` annotation. ## Usage -1) Add Pigeon as a `dev_dependency`. +1) Add pigeon as a `dev_dependency`. 1) Make a ".dart" file outside of your "lib" directory for defining the communication interface. 1) Run pigeon on your ".dart" file to generate the required Dart and host-language code: `flutter pub get` then `flutter pub run pigeon` - with suitable arguments (see [example](./example)). + with suitable arguments. [Example](./example/README.md#Invocation). 1) Add the generated Dart code to `./lib` for compilation. 1) Implement the host-language code and add it to your build (see below). 1) Call the generated Dart methods. +### Rules for defining your communication interface +[Example](./example/README.md#HostApi_Example) + +1) The file should contain no method or function definitions, only declarations. +1) Custom classes used by APIs are defined as classes with fields of the + supported datatypes (see the supported Datatypes section). +1) APIs should be defined as an `abstract class` with either `@HostApi()` or + `@FlutterApi()` as metadata. `@HostApi()` being for procedures that are defined + on the host platform and the `@FlutterApi()` for procedures that are defined in Dart. +1) Method declarations on the API classes should have arguments and a return + value whose types are defined in the file, are supported datatypes, or are + `void`. +1) Generics are supported, but can currently only be used with nullable types + (example: `List`). +1) Objc and Swift have special naming conventions that can be utilized with the + `@ObjCSelector` and `@SwiftFunction` respectively. + ### Flutter calling into iOS steps 1) Add the generated Objective-C or Swift code to your Xcode project for compilation @@ -58,214 +132,12 @@ doesn't need to worry about conflicting versions of Pigeon. ### Calling into Flutter from the host platform -Flutter also supports calling in the opposite direction. The steps are similar +Pigeon also supports calling in the opposite direction. The steps are similar but reversed. For more information look at the annotation `@FlutterApi()` which -denotes APIs that live in Flutter but are invoked from the host platform. - -### Rules for defining your communication interface - -1) The file should contain no method or function definitions, only declarations. -1) Custom classes used by APIs are defined as classes with fields of the - supported datatypes (see the supported Datatypes section). -1) APIs should be defined as an `abstract class` with either `HostApi()` or - `FlutterApi()` as metadata. The former being for procedures that are defined - on the host platform and the latter for procedures that are defined in Dart. -1) Method declarations on the API classes should have arguments and a return - value whose types are defined in the file, are supported datatypes, or are - `void`. -1) Generics are supported, but can currently only be used with nullable types - (example: `List`). - -## Supported Datatypes - -Pigeon uses the `StandardMessageCodec` so it supports any datatype Platform -Channels supports -[[documentation](https://flutter.dev/docs/development/platform-integration/platform-channels#codec)]. -Nested datatypes are supported, too. - -## Features - -### Asynchronous Handlers - -By default Pigeon will generate synchronous handlers for messages and -asynchronous methods. If you want a handler to be able to respond to a message -asynchronously you can use the @async annotation as of version 0.1.20. - -Example: - -```dart -class Value { - int? number; -} - -@HostApi() -abstract class Api2Host { - @async - Value calculate(Value value); -} -``` - -Generates: - -```objc -// Objective-C -@protocol Api2Host --(void)calculate:(nullable Value *)input - completion:(void(^)(Value *_Nullable, FlutterError *_Nullable))completion; -@end -``` - -```swift -// Swift - -/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/ -protocol Api2Host { - func calculate(value: Value, completion: @escaping (Value) -> Void) -} -``` - -```java -// Java -public interface Result { - void success(T result); -} - -/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/ -public interface Api2Host { - void calculate(Value arg, Result result); -} -``` - -```kotlin -// Kotlin - -/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/ -interface Api2Host { - fun calculate(value: Value, callback: (Result) -> Unit) -} -``` - -```c++ -// C++ - -/** Generated class from Pigeon that represents a handler of messages from Flutter.*/ -class Api2Host { -public: - virtual void calculate(Value value, flutter::MessageReply result) = 0; -} -``` - -### Null Safety (NNBD) - -Pigeon supports generating null-safe code, but it doesn't yet support: - -1) Nullable generics type arguments -1) Nullable enum arguments to methods - -### Enums - -Pigeon supports enum generation in class fields. For example: -```dart -enum State { - pending, - success, - error, -} - -class StateResult { - String? errorMessage; - State? state; -} - -@HostApi() -abstract class Api { - StateResult queryState(); -} -``` - -### Primitive Data-types - -Prior to version 1.0 all arguments to API methods had to be wrapped in a class, now they can be used directly. For example: - -```dart -@HostApi() -abstract class Api { - Map makeMap(List keys, List values); -} -``` - -### TaskQueues - -When targeting a Flutter version that supports the -[TaskQueue API](https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-kotlin-tab#channels-and-platform-threading) -the threading model for handling HostApi methods can be selected with the -`TaskQueue` annotation: - -```dart -@HostApi() -abstract class Api2Host { - @TaskQueue(type: TaskQueueType.serialBackgroundThread) - int add(int x, int y); -} -``` - -### Error Handling - -#### Kotlin, Java and Swift - -All Host API exceptions are translated into Flutter `PlatformException`. -* For synchronous methods, thrown exceptions will be caught and translated. -* For asynchronous methods, there is no default exception handling; errors should be returned via the provided callback. - -To pass custom details into `PlatformException` for error handling, use `FlutterError` in your Host API. -For example: - -```kotlin -// Kotlin -class MyApi : GeneratedApi { - // For synchronous methods - override fun doSomething() { - throw FlutterError('error_code', 'message', 'details') - } - - // For async methods - override fun doSomethingAsync(callback: (Result) -> Unit) { - callback(Result.failure(FlutterError('error_code', 'message', 'details')) - } -} -``` - -#### Objective-C and C++ - -Likewise, Host API errors can be sent using the provided `FlutterError` class (translated into `PlatformException`). - -For synchronous methods: -* Objective-C - Assign the `error` argument to a `FlutterError` reference. -* C++ - Return a `FlutterError` directly (for void methods) or within an `ErrorOr` instance. - -For async methods: -* Both - Return a `FlutterError` through the provided callback. - -#### Handling the errors - -Then you can implement error handling on the Flutter side: - -```dart -// Dart -void doSomething() { - try { - myApi.doSomething() - } catch (PlatformException e) { - if (e.code == 'error_code') { - // Perform custom error handling - assert(e.message == 'message') - assert(e.details == 'details') - } - } -} -``` +denotes APIs that live in Flutter but are invoked from the host platform. +[Example](./example/README.md#FlutterApi_Example). ## Feedback -File an issue in [flutter/flutter](https://github.com/flutter/flutter) with the -word "pigeon" in the title. +File an issue in [flutter/flutter](https://github.com/flutter/flutter) with +"[pigeon]" at the start of the title. diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md index c39421932005..a310f46b1302 100644 --- a/packages/pigeon/example/README.md +++ b/packages/pigeon/example/README.md @@ -1,155 +1,279 @@ + # Pigeon Examples +The examples here will cover basic usage. For a more thorough set of examples, +check the [core_tests pigeon file](../pigeons/core_tests.dart) and +[platform test folder](../platform_tests/) ([shared_test_plugin_code](../platform_tests/shared_test_plugin_code/) and [alternate_language_test_plugin](../platform_tests/alternate_language_test_plugin/) especially). + +## Invocation + +Begin by configuring pigeon at the top of the `.dart` input file. +In actual use, you would include only the languages +needed for your project. + + +```dart +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartOptions: DartOptions(), + cppOptions: CppOptions(namespace: 'pigeon_example'), + cppHeaderOut: 'windows/runner/messages.g.h', + cppSourceOut: 'windows/runner/messages.g.cpp', + kotlinOut: + 'android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt', + kotlinOptions: KotlinOptions(), + javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', + javaOptions: JavaOptions(), + swiftOut: 'ios/Runner/Messages.g.swift', + swiftOptions: SwiftOptions(), + objcHeaderOut: 'macos/Runner/messages.g.h', + objcSourceOut: 'macos/Runner/messages.g.m', + // Set this to a unique prefix for your plugin or application, per Objective-C naming conventions. + objcOptions: ObjcOptions(prefix: 'PGN'), + copyrightHeader: 'pigeons/copyright.txt', +)) +``` +Then make a simple call to run pigeon on the Dart file containing your definitions. + +```sh +flutter pub run pigeon --input path/to/input.dart +``` + ## HostApi Example This example gives an overview of how to use Pigeon to call into the -host-platform from Flutter. - -For instructions to set up your own Pigeon usage see these [steps](https://pub.dev/packages/pigeon#usage). +host platform from Flutter. -### message.dart +### Dart input This is the Pigeon file that describes the interface that will be used to call from Flutter to the host-platform. + ```dart -import 'package:pigeon/pigeon.dart'; - -class Book { - String? title; - String? author; +enum Code { one, two } + +class MessageData { + MessageData({required this.code, required this.data}); + String? name; + String? description; + Code code; + Map data; } @HostApi() -abstract class BookApi { - List search(String keyword); +abstract class ExampleHostApi { + String getHostLanguage(); + + // These annotations create more idiomatic naming of methods in Objc and Swift. + @ObjCSelector('addNumber:toNumber:') + @SwiftFunction('add(_:to:)') + int add(int a, int b); + + @async + bool sendMessage(MessageData message); } ``` -### invocation +### Dart -This an example call to Pigeon that would ingest a defintion file -`pigeons/message.dart` and generate corresponding output code for each -supported language. (In actual use, you would not normally use both Objective-C -and Swift, or both Java and Kotlin, but instead use just the languages matching -your project.) +This is the code that will use the generated Dart code to make calls from Flutter to +the host platform. -```sh -flutter pub run pigeon \ - --input pigeons/message.dart \ - --dart_out lib/pigeon.dart \ - --objc_header_out ios/Runner/pigeon.h \ - --objc_source_out ios/Runner/pigeon.m \ - --swift_out ios/Runner/Pigeon.swift \ - --kotlin_out android/app/src/main/kotlin/dev/flutter/pigeon/Pigeon.kt \ - --kotlin_package "dev.flutter.pigeon" \ - --java_out android/app/src/main/java/dev/flutter/pigeon/Pigeon.java \ - --java_package "dev.flutter.pigeon" \ - --cpp_header_out windows/runner/pigeon.h \ - --cpp_source_out windows/runner/pigeon.cpp \ - --cpp_namespace pigeon + +```dart +final ExampleHostApi _api = ExampleHostApi(); + +/// Calls host method `add` with provided arguments. +Future add(int a, int b) async { + try { + return await _api.add(a, b); + } catch (e) { + // handle error. + return 0; + } +} + +/// Sends message through host api using `MessageData` class +/// and api `sendMessage` method. +Future sendMessage(String messageText) { + final MessageData message = MessageData( + code: Code.one, + data: {'header': 'this is a header'}, + description: 'uri text', + ); + try { + return _api.sendMessage(message); + } catch (e) { + // handle error. + return Future(() => true); + } +} ``` -### AppDelegate.m +### Swift -This is the code that will use the generated Objective-C code to receive calls -from Flutter. +This is the code that will use the generated Swift code to receive calls from Flutter. +packages/pigeon/example/app/ios/Runner/AppDelegate.swift + +```swift +// This extension of Error is required to do use FlutterError in any Swift code. +extension FlutterError: Error {} -```objc -#import "AppDelegate.h" -#import -#import "pigeon.h" +private class PigeonApiImplementation: ExampleHostApi { + func getHostLanguage() throws -> String { + return "Swift" + } -@interface MyApi : NSObject -@end + func add(_ a: Int64, to b: Int64) throws -> Int64 { + if (a < 0 || b < 0) { + throw FlutterError(code: "code", message: "message", details: "details"); + } + return a + b + } -@implementation MyApi --(NSArray *)searchKeyword:(NSString *)keyword error:(FlutterError **)error { - Book *result = [[Book alloc] init]; - result.title = - [NSString stringWithFormat:@"%@'s Life", request.query]; - result.author = keyword; - return @[ result ]; -} -@end - -@implementation AppDelegate -- (BOOL)application:(UIApplication *)application -didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - MyApi *api = [[MyApi alloc] init]; - BookApiSetup(getFlutterEngine().binaryMessenger, api); - return YES; + func sendMessage(message: MessageData, completion: @escaping (Result) -> Void) { + if (message.code == Code.one) { + completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + return + } + completion(.success(true)) + } } -@end ``` -### AppDelegate.swift - -This is the code that will use the generated Swift code to receive calls from Flutter. +### Kotlin + +```kotlin +private class PigeonApiImplementation: ExampleHostApi { + override fun getHostLanguage(): String { + return "Kotlin" + } -```swift -import Flutter + override fun add(a: Long, b: Long): Long { + if (a < 0L || b < 0L) { + throw FlutterError("code", "message", "details"); + } + return a + b + } -class MyApi: NSObject, BookApi { - func search(keyword: String) -> [Book] { - let result = Book(title: "\(keyword)'s Life", author: keyword) - return [result] + override fun sendMessage(message: MessageData, callback: (Result) -> Unit) { + if (message.code == Code.ONE) { + callback(Result.failure(FlutterError("code", "message", "details"))) + return + } + callback(Result.success(true)) } } +``` -class AppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? - ) -> Bool { - let api = MyApi() - BookApiSetup.setUp(getFlutterEngine().binaryMessenger, api) - return true +### C++ + +```c++ +class PigeonApiImplementation : public ExampleHostApi { + public: + PigeonApiImplementation() {} + virtual ~PigeonApiImplementation() {} + + ErrorOr GetHostLanguage() override { return "C++"; } + ErrorOr Add(int64_t a, int64_t b) { + if (a < 0 || b < 0) { + return FlutterError("code", "message", "details"); + } + return a + b; + } + void SendMessage(const MessageData& message, + std::function reply)> result) { + if (message.code == Code.one) { + result(FlutterError("code", "message", "details")); + return; + } + result(true); } +}; +``` + +## FlutterApi Example + +This example gives an overview of how to use Pigeon to call into the Flutter +app from the host platform. + +### Dart input + + +```dart +@FlutterApi() +abstract class MessageFlutterApi { + String flutterMethod(String? aString); } ``` -### StartActivity.java +### Dart -This is the code that will use the generated Java code to receive calls from Flutter. +This is the code that will use the generated Dart code to handle calls made to +Flutter from the host platform. -```java -import dev.flutter.pigeon.Pigeon.*; -import java.util.Collections; + +```dart +class _ExampleFlutterApi implements MessageFlutterApi { + @override + String flutterMethod(String? aString) { + return aString ?? ''; + } +} +// ยทยทยท + MessageFlutterApi.setup(_ExampleFlutterApi()); +``` -public class StartActivity extends Activity { - private class MyApi implements BookApi { - List search(String keyword) { - Book result = new Book(); - result.author = keyword; - result.title = String.format("%s's Life", keyword); - return Collections.singletonList(result) - } +### Swift + + +```swift +private class PigeonFlutterApi { + var flutterAPI: MessageFlutterApi + + init(binaryMessenger: FlutterBinaryMessenger) { + flutterAPI = MessageFlutterApi(binaryMessenger: binaryMessenger) } - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - BookApi.setup(getBinaryMessenger(), new MyApi()); + func callFlutterMethod(aString aStringArg: String?, completion: @escaping (Result) -> Void) { + flutterAPI.flutterMethod(aString: aStringArg) { + completion(.success($0)) + } } } ``` -### test.dart +### Kotlin -This is the Dart code that will call into the host-platform using the generated -Dart code. + +```kotlin +private class PigeonFlutterApi { -```dart -import 'pigeon.dart'; - -void main() { - testWidgets("test pigeon", (WidgetTester tester) async { - Api api = Api(); - List reply = await api.search("Aaron"); - expect(reply[0].title, equals("Aaron's Life")); - }); + var flutterApi: MessageFlutterApi? = null + + constructor(binding: FlutterPlugin.FlutterPluginBinding) { + flutterApi = MessageFlutterApi(binding.getBinaryMessenger()) + } + + fun callFlutterMethod(aString: String, callback: (Result) -> Unit) { + flutterApi!!.flutterMethod(aString) { + echo -> callback(Result.success(echo)) + } + } } +``` + +### C++ + +```c++ +void TestPlugin::CallFlutterMethod( + String aString, std::function reply)> result) { + MessageFlutterApi->FlutterMethod( + aString, [result](String echo) { result(echo); }, + [result](const FlutterError& error) { result(error); }); +} ``` ## Swift / Kotlin Plugin Example 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 new file mode 100644 index 000000000000..c1c3b7a898d5 --- /dev/null +++ b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java @@ -0,0 +1,343 @@ +// 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 (v10.1.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) +public class Messages { + + /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ + public static class FlutterError extends RuntimeException { + + /** The error code. */ + public final String code; + + /** 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) { + super(message); + this.code = code; + this.details = details; + } + } + + @NonNull + protected static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList(3); + if (exception instanceof FlutterError) { + FlutterError error = (FlutterError) exception; + errorList.add(error.code); + errorList.add(error.getMessage()); + errorList.add(error.details); + } else { + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + } + return errorList; + } + + public enum Code { + ONE(0), + TWO(1); + + final int index; + + private Code(final int index) { + this.index = index; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class MessageData { + private @Nullable String name; + + public @Nullable String getName() { + return name; + } + + public void setName(@Nullable String setterArg) { + this.name = setterArg; + } + + private @Nullable String description; + + public @Nullable String getDescription() { + return description; + } + + public void setDescription(@Nullable String setterArg) { + this.description = setterArg; + } + + private @NonNull Code code; + + public @NonNull Code getCode() { + return code; + } + + public void setCode(@NonNull Code setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"code\" is null."); + } + this.code = setterArg; + } + + private @NonNull Map data; + + public @NonNull Map getData() { + return data; + } + + public void setData(@NonNull Map setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"data\" is null."); + } + this.data = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + MessageData() {} + + public static final class Builder { + + private @Nullable String name; + + public @NonNull Builder setName(@Nullable String setterArg) { + this.name = setterArg; + return this; + } + + private @Nullable String description; + + public @NonNull Builder setDescription(@Nullable String setterArg) { + this.description = setterArg; + return this; + } + + private @Nullable Code code; + + public @NonNull Builder setCode(@NonNull Code setterArg) { + this.code = setterArg; + return this; + } + + private @Nullable Map data; + + public @NonNull Builder setData(@NonNull Map setterArg) { + this.data = setterArg; + return this; + } + + public @NonNull MessageData build() { + MessageData pigeonReturn = new MessageData(); + pigeonReturn.setName(name); + pigeonReturn.setDescription(description); + pigeonReturn.setCode(code); + pigeonReturn.setData(data); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(name); + toListResult.add(description); + toListResult.add(code == null ? null : code.index); + toListResult.add(data); + return toListResult; + } + + static @NonNull MessageData fromList(@NonNull ArrayList list) { + MessageData pigeonResult = new MessageData(); + Object name = list.get(0); + pigeonResult.setName((String) name); + Object description = list.get(1); + pigeonResult.setDescription((String) description); + Object code = list.get(2); + pigeonResult.setCode(code == null ? null : Code.values()[(int) code]); + Object data = list.get(3); + pigeonResult.setData((Map) data); + return pigeonResult; + } + } + + public interface Result { + @SuppressWarnings("UnknownNullness") + void success(T result); + + void error(@NonNull Throwable error); + } + + private static class ExampleHostApiCodec extends StandardMessageCodec { + public static final ExampleHostApiCodec INSTANCE = new ExampleHostApiCodec(); + + private ExampleHostApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return MessageData.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof MessageData) { + stream.write(128); + writeValue(stream, ((MessageData) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface ExampleHostApi { + + @NonNull + String getHostLanguage(); + + @NonNull + Long add(@NonNull Long a, @NonNull Long b); + + void sendMessage(@NonNull MessageData message, @NonNull Result result); + + /** The codec used by ExampleHostApi. */ + static @NonNull MessageCodec getCodec() { + return ExampleHostApiCodec.INSTANCE; + } + /** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ExampleHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ExampleHostApi.getHostLanguage", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + String output = api.getHostLanguage(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ExampleHostApi.add", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number aArg = (Number) args.get(0); + Number bArg = (Number) args.get(1); + try { + Long output = + api.add( + (aArg == null) ? null : aArg.longValue(), + (bArg == null) ? null : bArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.ExampleHostApi.sendMessage", 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.sendMessage(messageArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class MessageFlutterApi { + private final @NonNull BinaryMessenger binaryMessenger; + + public MessageFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + /** Public interface for sending reply. */ + @SuppressWarnings("UnknownNullness") + public interface Reply { + void reply(T reply); + } + /** The codec used by MessageFlutterApi. */ + static @NonNull MessageCodec getCodec() { + return new StandardMessageCodec(); + } + + public void flutterMethod(@Nullable String aStringArg, @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.MessageFlutterApi.flutterMethod", getCodec()); + channel.send( + new ArrayList(Collections.singletonList(aStringArg)), + channelReply -> { + @SuppressWarnings("ConstantConditions") + String output = (String) channelReply; + callback.reply(output); + }); + } + } +} 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 51f7d68df048..3f7d3b3362eb 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 @@ -5,21 +5,60 @@ package dev.flutter.pigeon_example_app import ExampleHostApi +import MessageData +import MessageFlutterApi +import FlutterError + import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.plugins.FlutterPlugin +// #docregion kotlin-class private class PigeonApiImplementation: ExampleHostApi { - override fun getHostLanguage(): String { - return "Kotlin" + override fun getHostLanguage(): String { + return "Kotlin" + } + + override fun add(a: Long, b: Long): Long { + if (a < 0L || b < 0L) { + throw FlutterError("code", "message", "details"); + } + return a + b + } + + override fun sendMessage(message: MessageData, callback: (Result) -> Unit) { + if (message.code == Code.ONE) { + callback(Result.failure(FlutterError("code", "message", "details"))) + return } + callback(Result.success(true)) + } } +// #enddocregion kotlin-class -class MainActivity: FlutterActivity() { - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) +// #docregion kotlin-class-flutter +private class PigeonFlutterApi { + + var flutterApi: MessageFlutterApi? = null - val api = PigeonApiImplementation() - ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api); + constructor(binding: FlutterPlugin.FlutterPluginBinding) { + flutterApi = MessageFlutterApi(binding.getBinaryMessenger()) + } + + fun callFlutterMethod(aString: String, callback: (Result) -> Unit) { + flutterApi!!.flutterMethod(aString) { + echo -> callback(Result.success(echo)) } + } +} +// #enddocregion kotlin-class-flutter + +class MainActivity: FlutterActivity() { + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + val api = PigeonApiImplementation() + ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api); + } } 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 fe157fcf2e9b..bc2dfa818ad7 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,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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v10.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -44,14 +44,79 @@ class FlutterError ( override val message: String? = null, val details: Any? = null ) : Throwable() + +enum class Code(val raw: Int) { + ONE(0), + TWO(1); + + companion object { + fun ofRaw(raw: Int): Code? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** 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 + +) { + companion object { + @Suppress("UNCHECKED_CAST") + fun fromList(list: List): MessageData { + val name = list[0] as String? + val description = list[1] as String? + val code = Code.ofRaw(list[2] as Int)!! + val data = list[3] as Map + return MessageData(name, description, code, data) + } + } + fun toList(): List { + return listOf( + name, + description, + code.raw, + data, + ) + } +} + +@Suppress("UNCHECKED_CAST") +private object ExampleHostApiCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + return (readValue(buffer) as? List)?.let { + MessageData.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is MessageData -> { + stream.write(128) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + /** 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) companion object { /** The codec used by ExampleHostApi. */ val codec: MessageCodec by lazy { - StandardMessageCodec() + ExampleHostApiCodec } /** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */ @Suppress("UNCHECKED_CAST") @@ -72,6 +137,62 @@ interface ExampleHostApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.ExampleHostApi.add", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val aArg = args[0].let { if (it is Int) it.toLong() else it as Long } + val bArg = args[1].let { if (it is Int) it.toLong() else it as Long } + var wrapped: List + try { + wrapped = listOf(api.add(aArg, bArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.ExampleHostApi.sendMessage", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val messageArg = args[0] as MessageData + api.sendMessage(messageArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +@Suppress("UNCHECKED_CAST") +class MessageFlutterApi(private val binaryMessenger: BinaryMessenger) { + companion object { + /** The codec used by MessageFlutterApi. */ + val codec: MessageCodec by lazy { + StandardMessageCodec() + } + } + fun flutterMethod(aStringArg: String?, callback: (String) -> Unit) { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.MessageFlutterApi.flutterMethod", codec) + channel.send(listOf(aStringArg)) { + val result = it as String + callback(result) } } } diff --git a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift index f8319d318edd..3c7913122701 100644 --- a/packages/pigeon/example/app/ios/Runner/AppDelegate.swift +++ b/packages/pigeon/example/app/ios/Runner/AppDelegate.swift @@ -5,11 +5,47 @@ import Flutter import UIKit +// #docregion swift-class +// This extension of Error is required to do use FlutterError in any Swift code. +extension FlutterError: Error {} + private class PigeonApiImplementation: ExampleHostApi { func getHostLanguage() throws -> String { return "Swift" } + + func add(_ a: Int64, to b: Int64) throws -> Int64 { + if (a < 0 || b < 0) { + throw FlutterError(code: "code", message: "message", details: "details"); + } + return a + b + } + + func sendMessage(message: MessageData, completion: @escaping (Result) -> Void) { + if (message.code == Code.one) { + completion(.failure(FlutterError(code: "code", message: "message", details: "details"))) + return + } + completion(.success(true)) + } +} +// #enddocregion swift-class + +// #docregion swift-class-flutter +private class PigeonFlutterApi { + var flutterAPI: MessageFlutterApi + + init(binaryMessenger: FlutterBinaryMessenger) { + flutterAPI = MessageFlutterApi(binaryMessenger: binaryMessenger) + } + + func callFlutterMethod(aString aStringArg: String?, completion: @escaping (Result) -> Void) { + flutterAPI.flutterMethod(aString: aStringArg) { + completion(.success($0)) + } + } } +// #enddocregion swift-class-flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { diff --git a/packages/pigeon/example/app/ios/Runner/Messages.g.swift b/packages/pigeon/example/app/ios/Runner/Messages.g.swift index 4223267f583c..8dd1a6b8878c 100644 --- a/packages/pigeon/example/app/ios/Runner/Messages.g.swift +++ b/packages/pigeon/example/app/ios/Runner/Messages.g.swift @@ -1,17 +1,16 @@ // 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v10.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation - #if os(iOS) - import Flutter +import Flutter #elseif os(macOS) - import FlutterMacOS +import FlutterMacOS #else - #error("Unsupported platform.") +#error("Unsupported platform.") #endif private func wrapResult(_ result: Any?) -> [Any?] { @@ -23,32 +22,106 @@ private func wrapError(_ error: Any) -> [Any?] { return [ flutterError.code, flutterError.message, - flutterError.details, + flutterError.details ] } return [ "\(error)", "\(type(of: error))", - "Stacktrace: \(Thread.callStackSymbols)", + "Stacktrace: \(Thread.callStackSymbols)" ] } private func nilOrValue(_ value: Any?) -> T? { if value is NSNull { return nil } - return (value as Any) as! T? + return value as! T? +} + +enum Code: Int { + case one = 0 + case two = 1 +} + +/// Generated class from Pigeon that represents data sent in messages. +struct MessageData { + var name: String? = nil + var description: String? = nil + var code: Code + var data: [String?: String?] + + static func fromList(_ list: [Any?]) -> MessageData? { + let name: String? = nilOrValue(list[0]) + let description: String? = nilOrValue(list[1]) + let code = Code(rawValue: list[2] as! Int)! + let data = list[3] as! [String?: String?] + + return MessageData( + name: name, + description: description, + code: code, + data: data + ) + } + func toList() -> [Any?] { + return [ + name, + description, + code.rawValue, + data, + ] + } +} + +private class ExampleHostApiCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return MessageData.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class ExampleHostApiCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? MessageData { + super.writeByte(128) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class ExampleHostApiCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return ExampleHostApiCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return ExampleHostApiCodecWriter(data: data) + } +} + +class ExampleHostApiCodec: FlutterStandardMessageCodec { + static let shared = ExampleHostApiCodec(readerWriter: ExampleHostApiCodecReaderWriter()) } + /// 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) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class ExampleHostApiSetup { /// The codec used by ExampleHostApi. + static var codec: FlutterStandardMessageCodec { ExampleHostApiCodec.shared } /// Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ExampleHostApi?) { - let getHostLanguageChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.ExampleHostApi.getHostLanguage", binaryMessenger: binaryMessenger) + let getHostLanguageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.ExampleHostApi.getHostLanguage", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getHostLanguageChannel.setMessageHandler { _, reply in do { @@ -61,5 +134,52 @@ class ExampleHostApiSetup { } else { getHostLanguageChannel.setMessageHandler(nil) } + let addChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.ExampleHostApi.add", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + addChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let aArg = args[0] is Int64 ? args[0] as! Int64 : Int64(args[0] as! Int32) + let bArg = args[1] is Int64 ? args[1] as! Int64 : Int64(args[1] as! Int32) + do { + let result = try api.add(aArg, to: bArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + addChannel.setMessageHandler(nil) + } + let sendMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.ExampleHostApi.sendMessage", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + sendMessageChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let messageArg = args[0] as! MessageData + api.sendMessage(message: messageArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + sendMessageChannel.setMessageHandler(nil) + } + } +} +/// Generated class from Pigeon that represents Flutter messages that can be called from Swift. +class MessageFlutterApi { + private let binaryMessenger: FlutterBinaryMessenger + init(binaryMessenger: FlutterBinaryMessenger){ + self.binaryMessenger = binaryMessenger + } + func flutterMethod(aString aStringArg: String?, completion: @escaping (String) -> Void) { + let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.MessageFlutterApi.flutterMethod", binaryMessenger: binaryMessenger) + channel.sendMessage([aStringArg] as [Any?]) { response in + let result = response as! String + completion(result) + } } } diff --git a/packages/pigeon/example/app/lib/main.dart b/packages/pigeon/example/app/lib/main.dart index 93bfea02d376..ea90231c7565 100644 --- a/packages/pigeon/example/app/lib/main.dart +++ b/packages/pigeon/example/app/lib/main.dart @@ -9,7 +9,19 @@ import 'package:flutter/services.dart'; import 'src/messages.g.dart'; +// #docregion main-dart-flutter +class _ExampleFlutterApi implements MessageFlutterApi { + @override + String flutterMethod(String? aString) { + return aString ?? ''; + } +} +// #enddocregion main-dart-flutter + void main() { +// #docregion main-dart-flutter + MessageFlutterApi.setup(_ExampleFlutterApi()); +// #enddocregion main-dart-flutter runApp(const MyApp()); } @@ -42,6 +54,36 @@ class _MyHomePageState extends State { final ExampleHostApi _hostApi = ExampleHostApi(); String? _hostCallResult; + // #docregion main-dart + final ExampleHostApi _api = ExampleHostApi(); + + /// Calls host method `add` with provided arguments. + Future add(int a, int b) async { + try { + return await _api.add(a, b); + } catch (e) { + // handle error. + return 0; + } + } + + /// Sends message through host api using `MessageData` class + /// and api `sendMessage` method. + Future sendMessage(String messageText) { + final MessageData message = MessageData( + code: Code.one, + data: {'header': 'this is a header'}, + description: 'uri text', + ); + try { + return _api.sendMessage(message); + } catch (e) { + // handle error. + return Future(() => true); + } + } + // #enddocregion main-dart + @override void initState() { super.initState(); diff --git a/packages/pigeon/example/app/lib/src/messages.g.dart b/packages/pigeon/example/app/lib/src/messages.g.dart index e2cc26b87bd1..25e75e3f1ab9 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v10.1.1), 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 @@ -11,6 +11,70 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +enum Code { + one, + two, +} + +class MessageData { + MessageData({ + this.name, + this.description, + required this.code, + required this.data, + }); + + String? name; + + String? description; + + Code code; + + Map data; + + Object encode() { + return [ + name, + description, + code.index, + data, + ]; + } + + static MessageData decode(Object result) { + result as List; + return MessageData( + name: result[0] as String?, + description: result[1] as String?, + code: Code.values[result[2]! as int], + data: (result[3] as Map?)!.cast(), + ); + } +} + +class _ExampleHostApiCodec extends StandardMessageCodec { + const _ExampleHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is MessageData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return MessageData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + class ExampleHostApi { /// Constructor for [ExampleHostApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default @@ -19,7 +83,7 @@ class ExampleHostApi { : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; - static const MessageCodec codec = StandardMessageCodec(); + static const MessageCodec codec = _ExampleHostApiCodec(); Future getHostLanguage() async { final BasicMessageChannel channel = BasicMessageChannel( @@ -46,4 +110,85 @@ class ExampleHostApi { return (replyList[0] as String?)!; } } + + Future add(int arg_a, int arg_b) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ExampleHostApi.add', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_a, arg_b]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as int?)!; + } + } + + Future sendMessage(MessageData arg_message) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ExampleHostApi.sendMessage', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_message]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } +} + +abstract class MessageFlutterApi { + static const MessageCodec codec = StandardMessageCodec(); + + String flutterMethod(String? aString); + + static void setup(MessageFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.MessageFlutterApi.flutterMethod', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.MessageFlutterApi.flutterMethod was null.'); + final List args = (message as List?)!; + final String? arg_aString = (args[0] as String?); + final String output = api.flutterMethod(arg_aString); + return output; + }); + } + } + } } diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h new file mode 100644 index 000000000000..d789948007bb --- /dev/null +++ b/packages/pigeon/example/app/macos/Runner/messages.g.h @@ -0,0 +1,62 @@ +// 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 (v10.1.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#import + +@protocol FlutterBinaryMessenger; +@protocol FlutterMessageCodec; +@class FlutterError; +@class FlutterStandardTypedData; + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, PGNCode) { + PGNCodeOne = 0, + PGNCodeTwo = 1, +}; + +@class PGNMessageData; + +@interface PGNMessageData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithName:(nullable NSString *)name + description:(nullable NSString *)description + code:(PGNCode)code + data:(NSDictionary *)data; +@property(nonatomic, copy, nullable) NSString *name; +@property(nonatomic, copy, nullable) NSString *description; +@property(nonatomic, assign) PGNCode code; +@property(nonatomic, strong) NSDictionary *data; +@end + +/// The codec used by PGNExampleHostApi. +NSObject *PGNExampleHostApiGetCodec(void); + +@protocol PGNExampleHostApi +/// @return `nil` only when `error != nil`. +- (nullable NSString *)getHostLanguageWithError:(FlutterError *_Nullable *_Nonnull)error; +/// @return `nil` only when `error != nil`. +- (nullable NSNumber *)addNumber:(NSNumber *)a + toNumber:(NSNumber *)b + error:(FlutterError *_Nullable *_Nonnull)error; +- (void)sendMessageMessage:(PGNMessageData *)message + completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +@end + +extern void PGNExampleHostApiSetup(id binaryMessenger, + NSObject *_Nullable api); + +/// The codec used by PGNMessageFlutterApi. +NSObject *PGNMessageFlutterApiGetCodec(void); + +@interface PGNMessageFlutterApi : NSObject +- (instancetype)initWithBinaryMessenger:(id)binaryMessenger; +- (void)flutterMethodAString:(nullable NSString *)aString + completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.m b/packages/pigeon/example/app/macos/Runner/messages.g.m new file mode 100644 index 000000000000..8aa843612a55 --- /dev/null +++ b/packages/pigeon/example/app/macos/Runner/messages.g.m @@ -0,0 +1,217 @@ +// 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 (v10.1.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#import "messages.g.h" + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#if !__has_feature(objc_arc) +#error File requires ARC to be enabled. +#endif + +static NSArray *wrapResult(id result, FlutterError *error) { + if (error) { + return @[ + error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] + ]; + } + return @[ result ?: [NSNull null] ]; +} +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { + id result = array[key]; + return (result == [NSNull null]) ? nil : result; +} + +@interface PGNMessageData () ++ (PGNMessageData *)fromList:(NSArray *)list; ++ (nullable PGNMessageData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@implementation PGNMessageData ++ (instancetype)makeWithName:(nullable NSString *)name + description:(nullable NSString *)description + code:(PGNCode)code + data:(NSDictionary *)data { + PGNMessageData *pigeonResult = [[PGNMessageData alloc] init]; + pigeonResult.name = name; + pigeonResult.description = description; + pigeonResult.code = code; + pigeonResult.data = data; + return pigeonResult; +} ++ (PGNMessageData *)fromList:(NSArray *)list { + PGNMessageData *pigeonResult = [[PGNMessageData alloc] init]; + pigeonResult.name = GetNullableObjectAtIndex(list, 0); + pigeonResult.description = GetNullableObjectAtIndex(list, 1); + pigeonResult.code = [GetNullableObjectAtIndex(list, 2) integerValue]; + pigeonResult.data = GetNullableObjectAtIndex(list, 3); + NSAssert(pigeonResult.data != nil, @""); + return pigeonResult; +} ++ (nullable PGNMessageData *)nullableFromList:(NSArray *)list { + return (list) ? [PGNMessageData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.name ?: [NSNull null]), + (self.description ?: [NSNull null]), + @(self.code), + (self.data ?: [NSNull null]), + ]; +} +@end + +@interface PGNExampleHostApiCodecReader : FlutterStandardReader +@end +@implementation PGNExampleHostApiCodecReader +- (nullable id)readValueOfType:(UInt8)type { + switch (type) { + case 128: + return [PGNMessageData fromList:[self readValue]]; + default: + return [super readValueOfType:type]; + } +} +@end + +@interface PGNExampleHostApiCodecWriter : FlutterStandardWriter +@end +@implementation PGNExampleHostApiCodecWriter +- (void)writeValue:(id)value { + if ([value isKindOfClass:[PGNMessageData class]]) { + [self writeByte:128]; + [self writeValue:[value toList]]; + } else { + [super writeValue:value]; + } +} +@end + +@interface PGNExampleHostApiCodecReaderWriter : FlutterStandardReaderWriter +@end +@implementation PGNExampleHostApiCodecReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[PGNExampleHostApiCodecWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[PGNExampleHostApiCodecReader alloc] initWithData:data]; +} +@end + +NSObject *PGNExampleHostApiGetCodec(void) { + static FlutterStandardMessageCodec *sSharedObject = nil; + static dispatch_once_t sPred = 0; + dispatch_once(&sPred, ^{ + PGNExampleHostApiCodecReaderWriter *readerWriter = + [[PGNExampleHostApiCodecReaderWriter alloc] init]; + sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return sSharedObject; +} + +void PGNExampleHostApiSetup(id binaryMessenger, + NSObject *api) { + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.ExampleHostApi.getHostLanguage" + binaryMessenger:binaryMessenger + codec:PGNExampleHostApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(getHostLanguageWithError:)], + @"PGNExampleHostApi api (%@) doesn't respond to @selector(getHostLanguageWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + NSString *output = [api getHostLanguageWithError:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.ExampleHostApi.add" + binaryMessenger:binaryMessenger + codec:PGNExampleHostApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(addNumber:toNumber:error:)], + @"PGNExampleHostApi api (%@) doesn't respond to @selector(addNumber:toNumber:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSNumber *arg_a = GetNullableObjectAtIndex(args, 0); + NSNumber *arg_b = GetNullableObjectAtIndex(args, 1); + FlutterError *error; + NSNumber *output = [api addNumber:arg_a toNumber:arg_b error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.ExampleHostApi.sendMessage" + binaryMessenger:binaryMessenger + codec:PGNExampleHostApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(sendMessageMessage:completion:)], + @"PGNExampleHostApi api (%@) doesn't respond to " + @"@selector(sendMessageMessage:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + PGNMessageData *arg_message = GetNullableObjectAtIndex(args, 0); + [api sendMessageMessage:arg_message + completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } +} +NSObject *PGNMessageFlutterApiGetCodec(void) { + static FlutterStandardMessageCodec *sSharedObject = nil; + sSharedObject = [FlutterStandardMessageCodec sharedInstance]; + return sSharedObject; +} + +@interface PGNMessageFlutterApi () +@property(nonatomic, strong) NSObject *binaryMessenger; +@end + +@implementation PGNMessageFlutterApi + +- (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { + self = [super init]; + if (self) { + _binaryMessenger = binaryMessenger; + } + return self; +} +- (void)flutterMethodAString:(nullable NSString *)arg_aString + completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.MessageFlutterApi.flutterMethod" + binaryMessenger:self.binaryMessenger + codec:PGNMessageFlutterApiGetCodec()]; + [channel sendMessage:@[ arg_aString ?: [NSNull null] ] + reply:^(id reply) { + NSString *output = reply; + completion(output, nil); + }]; +} +@end diff --git a/packages/pigeon/example/app/pigeons/messages.dart b/packages/pigeon/example/app/pigeons/messages.dart index bb684c1ebb22..06216300eb68 100644 --- a/packages/pigeon/example/app/pigeons/messages.dart +++ b/packages/pigeon/example/app/pigeons/messages.dart @@ -4,18 +4,58 @@ import 'package:pigeon/pigeon.dart'; +// #docregion config @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', + dartOptions: DartOptions(), cppOptions: CppOptions(namespace: 'pigeon_example'), cppHeaderOut: 'windows/runner/messages.g.h', cppSourceOut: 'windows/runner/messages.g.cpp', kotlinOut: 'android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt', - // This file is also used by the macOS project. + kotlinOptions: KotlinOptions(), + javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', + javaOptions: JavaOptions(), swiftOut: 'ios/Runner/Messages.g.swift', + swiftOptions: SwiftOptions(), + objcHeaderOut: 'macos/Runner/messages.g.h', + objcSourceOut: 'macos/Runner/messages.g.m', + // Set this to a unique prefix for your plugin or application, per Objective-C naming conventions. + objcOptions: ObjcOptions(prefix: 'PGN'), copyrightHeader: 'pigeons/copyright.txt', )) +// #enddocregion config + +// This file and ./messages_test.dart must be identical below this line. + +// #docregion host-definitions +enum Code { one, two } + +class MessageData { + MessageData({required this.code, required this.data}); + String? name; + String? description; + Code code; + Map data; +} + @HostApi() abstract class ExampleHostApi { String getHostLanguage(); + + // These annotations create more idiomatic naming of methods in Objc and Swift. + @ObjCSelector('addNumber:toNumber:') + @SwiftFunction('add(_:to:)') + int add(int a, int b); + + @async + bool sendMessage(MessageData message); +} +// #enddocregion host-definitions + +// #docregion flutter-definitions +@FlutterApi() +abstract class MessageFlutterApi { + String flutterMethod(String? aString); } +// #enddocregion flutter-definitions diff --git a/packages/pigeon/example/app/pubspec.yaml b/packages/pigeon/example/app/pubspec.yaml index 674c59636a4b..8f7089a536e9 100644 --- a/packages/pigeon/example/app/pubspec.yaml +++ b/packages/pigeon/example/app/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: sdk: flutter dev_dependencies: + build_runner: ^2.1.10 flutter_test: sdk: flutter integration_test: diff --git a/packages/pigeon/example/app/windows/runner/flutter_window.cpp b/packages/pigeon/example/app/windows/runner/flutter_window.cpp index a1590d951add..d4f0fec67470 100644 --- a/packages/pigeon/example/app/windows/runner/flutter_window.cpp +++ b/packages/pigeon/example/app/windows/runner/flutter_window.cpp @@ -14,13 +14,29 @@ namespace { using pigeon_example::ErrorOr; using pigeon_example::ExampleHostApi; +// #docregion cpp-class class PigeonApiImplementation : public ExampleHostApi { public: PigeonApiImplementation() {} virtual ~PigeonApiImplementation() {} ErrorOr GetHostLanguage() override { return "C++"; } + ErrorOr Add(int64_t a, int64_t b) { + if (a < 0 || b < 0) { + return FlutterError("code", "message", "details"); + } + return a + b; + } + void SendMessage(const MessageData& message, + std::function reply)> result) { + if (message.code == Code.one) { + result(FlutterError("code", "message", "details")); + return; + } + result(true); + } }; +// #enddocregion cpp-class } // namespace FlutterWindow::FlutterWindow(const flutter::DartProject& project) @@ -33,6 +49,15 @@ bool FlutterWindow::OnCreate() { return false; } + // #docregion cpp-method-flutter + void TestPlugin::CallFlutterMethod( + String aString, std::function reply)> result) { + MessageFlutterApi->FlutterMethod( + aString, [result](String echo) { result(echo); }, + [result](const FlutterError& error) { result(error); }); + } + // #enddocregion cpp-method-flutter + RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface diff --git a/packages/pigeon/example/app/windows/runner/messages.g.cpp b/packages/pigeon/example/app/windows/runner/messages.g.cpp index 4a29cf23c566..ba7c06bb697c 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v10.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -24,10 +24,109 @@ using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; +// MessageData + +MessageData::MessageData(const Code& code, const EncodableMap& data) + : code_(code), data_(data) {} + +MessageData::MessageData(const std::string* name, + const std::string* description, const Code& code, + const EncodableMap& data) + : name_(name ? std::optional(*name) : std::nullopt), + description_(description ? std::optional(*description) + : std::nullopt), + code_(code), + data_(data) {} + +const std::string* MessageData::name() const { + return name_ ? &(*name_) : nullptr; +} + +void MessageData::set_name(const std::string_view* value_arg) { + name_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void MessageData::set_name(std::string_view value_arg) { name_ = value_arg; } + +const std::string* MessageData::description() const { + return description_ ? &(*description_) : nullptr; +} + +void MessageData::set_description(const std::string_view* value_arg) { + description_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void MessageData::set_description(std::string_view value_arg) { + description_ = value_arg; +} + +const Code& MessageData::code() const { return code_; } + +void MessageData::set_code(const Code& value_arg) { code_ = value_arg; } + +const EncodableMap& MessageData::data() const { return data_; } + +void MessageData::set_data(const EncodableMap& value_arg) { data_ = value_arg; } + +EncodableList MessageData::ToEncodableList() const { + EncodableList list; + list.reserve(4); + list.push_back(name_ ? EncodableValue(*name_) : EncodableValue()); + list.push_back(description_ ? EncodableValue(*description_) + : EncodableValue()); + list.push_back(EncodableValue((int)code_)); + list.push_back(EncodableValue(data_)); + return list; +} + +MessageData MessageData::FromEncodableList(const EncodableList& list) { + MessageData decoded((Code)(std::get(list[2])), + std::get(list[3])); + auto& encodable_name = list[0]; + if (!encodable_name.IsNull()) { + decoded.set_name(std::get(encodable_name)); + } + auto& encodable_description = list[1]; + if (!encodable_description.IsNull()) { + decoded.set_description(std::get(encodable_description)); + } + return decoded; +} + +ExampleHostApiCodecSerializer::ExampleHostApiCodecSerializer() {} + +EncodableValue ExampleHostApiCodecSerializer::ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const { + switch (type) { + case 128: + return CustomEncodableValue(MessageData::FromEncodableList( + std::get(ReadValue(stream)))); + default: + return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + } +} + +void ExampleHostApiCodecSerializer::WriteValue( + const EncodableValue& value, flutter::ByteStreamWriter* stream) const { + if (const CustomEncodableValue* custom_value = + std::get_if(&value)) { + if (custom_value->type() == typeid(MessageData)) { + stream->WriteByte(128); + WriteValue( + EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), + stream); + return; + } + } + flutter::StandardCodecSerializer::WriteValue(value, stream); +} + /// The codec used by ExampleHostApi. const flutter::StandardMessageCodec& ExampleHostApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( - &flutter::StandardCodecSerializer::GetInstance()); + &ExampleHostApiCodecSerializer::GetInstance()); } // Sets up an instance of `ExampleHostApi` to handle messages through the @@ -59,6 +158,78 @@ void ExampleHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, channel->SetMessageHandler(nullptr); } } + { + auto channel = std::make_unique>( + binary_messenger, "dev.flutter.pigeon.ExampleHostApi.add", &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_a_arg = args.at(0); + if (encodable_a_arg.IsNull()) { + reply(WrapError("a_arg unexpectedly null.")); + return; + } + const int64_t a_arg = encodable_a_arg.LongValue(); + const auto& encodable_b_arg = args.at(1); + if (encodable_b_arg.IsNull()) { + reply(WrapError("b_arg unexpectedly null.")); + return; + } + const int64_t b_arg = encodable_b_arg.LongValue(); + ErrorOr output = api->Add(a_arg, b_arg); + 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); + } + } + { + auto channel = std::make_unique>( + binary_messenger, "dev.flutter.pigeon.ExampleHostApi.sendMessage", + &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->SendMessage(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) { @@ -73,4 +244,37 @@ EncodableValue ExampleHostApi::WrapError(const FlutterError& error) { error.details()}); } +// Generated class from Pigeon that represents Flutter messages that can be +// called from C++. +MessageFlutterApi::MessageFlutterApi(flutter::BinaryMessenger* binary_messenger) + : binary_messenger_(binary_messenger) {} + +const flutter::StandardMessageCodec& MessageFlutterApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &flutter::StandardCodecSerializer::GetInstance()); +} + +void MessageFlutterApi::FlutterMethod( + const std::string* a_string_arg, + std::function&& on_success, + std::function&& on_error) { + auto channel = std::make_unique>( + binary_messenger_, "dev.flutter.pigeon.MessageFlutterApi.flutterMethod", + &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + a_string_arg ? EncodableValue(*a_string_arg) : EncodableValue(), + }); + channel->Send( + encoded_api_arguments, + [on_success = std::move(on_success), on_error = std::move(on_error)]( + const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = + GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto& return_value = + std::get(encodable_return_value); + on_success(return_value); + }); +} + } // namespace pigeon_example diff --git a/packages/pigeon/example/app/windows/runner/messages.g.h b/packages/pigeon/example/app/windows/runner/messages.g.h index 53d659a2070e..1bf5dddf798a 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 (v9.2.5), do not edit directly. +// Autogenerated from Pigeon (v10.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -52,12 +52,68 @@ class ErrorOr { private: friend class ExampleHostApi; + friend class MessageFlutterApi; ErrorOr() = default; T TakeValue() && { return std::get(std::move(v_)); } std::variant v_; }; +enum class Code { one = 0, two = 1 }; + +// Generated class from Pigeon that represents data sent in messages. +class MessageData { + public: + // Constructs an object setting all non-nullable fields. + explicit MessageData(const Code& code, const flutter::EncodableMap& data); + + // Constructs an object setting all fields. + explicit MessageData(const std::string* name, const std::string* description, + const Code& code, const flutter::EncodableMap& data); + + const std::string* name() const; + void set_name(const std::string_view* value_arg); + void set_name(std::string_view value_arg); + + const std::string* description() const; + void set_description(const std::string_view* value_arg); + void set_description(std::string_view value_arg); + + const Code& code() const; + void set_code(const Code& value_arg); + + const flutter::EncodableMap& data() const; + void set_data(const flutter::EncodableMap& value_arg); + + private: + static MessageData FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class ExampleHostApi; + friend class ExampleHostApiCodecSerializer; + friend class MessageFlutterApi; + friend class MessageFlutterApiCodecSerializer; + std::optional name_; + std::optional description_; + Code code_; + flutter::EncodableMap data_; +}; + +class ExampleHostApiCodecSerializer : public flutter::StandardCodecSerializer { + public: + ExampleHostApiCodecSerializer(); + inline static ExampleHostApiCodecSerializer& GetInstance() { + static ExampleHostApiCodecSerializer sInstance; + return sInstance; + } + + void WriteValue(const flutter::EncodableValue& value, + flutter::ByteStreamWriter* stream) const override; + + protected: + flutter::EncodableValue ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const override; +}; + // Generated interface from Pigeon that represents a handler of messages from // Flutter. class ExampleHostApi { @@ -66,6 +122,9 @@ class ExampleHostApi { ExampleHostApi& operator=(const ExampleHostApi&) = delete; virtual ~ExampleHostApi() {} virtual ErrorOr GetHostLanguage() = 0; + virtual ErrorOr Add(int64_t a, int64_t b) = 0; + virtual void SendMessage(const MessageData& message, + std::function reply)> result) = 0; // The codec used by ExampleHostApi. static const flutter::StandardMessageCodec& GetCodec(); @@ -79,5 +138,19 @@ class ExampleHostApi { protected: ExampleHostApi() = default; }; +// Generated class from Pigeon that represents Flutter messages that can be +// called from C++. +class MessageFlutterApi { + public: + MessageFlutterApi(flutter::BinaryMessenger* binary_messenger); + static const flutter::StandardMessageCodec& GetCodec(); + void FlutterMethod(const std::string* a_string, + std::function&& on_success, + std::function&& on_error); + + private: + flutter::BinaryMessenger* binary_messenger_; +}; + } // namespace pigeon_example #endif // PIGEON_MESSAGES_G_H_ diff --git a/packages/pigeon/example/build.excerpt.yaml b/packages/pigeon/example/build.excerpt.yaml new file mode 100644 index 000000000000..c0ce017a926d --- /dev/null +++ b/packages/pigeon/example/build.excerpt.yaml @@ -0,0 +1,23 @@ +targets: + $default: + sources: + include: + - lib/** + - pigeons/** + - app/** + # Some default includes that aren't really used here but will prevent + # false-negative warnings: + - $package$ + - lib/$lib$ + exclude: + - '**/.*/**' + - '**/build/**' + builders: + code_excerpter|code_excerpter: + enabled: true + generate_for: + - '**.dart' + - '**.swift' + - '**.kt' + - '**.cpp' + - '**.h' \ No newline at end of file diff --git a/packages/pigeon/example/lib/main.dart b/packages/pigeon/example/lib/main.dart new file mode 100644 index 000000000000..e7217c7d7b3f --- /dev/null +++ b/packages/pigeon/example/lib/main.dart @@ -0,0 +1,3 @@ +// 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. diff --git a/packages/pigeon/example/pubspec.yaml b/packages/pigeon/example/pubspec.yaml new file mode 100644 index 000000000000..af03bc09866f --- /dev/null +++ b/packages/pigeon/example/pubspec.yaml @@ -0,0 +1,11 @@ +name: pigeon_example +description: example app to show basic usage of pigeon. +publish_to: none + +environment: + sdk: ">=2.18.0 <4.0.0" + +dependencies: + +dev_dependencies: + build_runner: ^2.1.10 diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index ce2d0655d2b0..2b67bd552c45 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -11,7 +11,7 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '10.1.0'; +const String pigeonVersion = '10.1.1'; /// Read all the content from [stdin] to a String. String readStdin() { diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 09ea9133adcc..87a150edf44e 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -168,27 +168,29 @@ class Error { /// Options used when running the code generator. class PigeonOptions { /// Creates a instance of PigeonOptions - const PigeonOptions( - {this.input, - this.dartOut, - this.dartTestOut, - this.objcHeaderOut, - this.objcSourceOut, - this.objcOptions, - this.javaOut, - this.javaOptions, - this.swiftOut, - this.swiftOptions, - this.kotlinOut, - this.kotlinOptions, - this.cppHeaderOut, - this.cppSourceOut, - this.cppOptions, - this.dartOptions, - this.copyrightHeader, - this.oneLanguage, - this.astOut, - this.debugGenerators}); + const PigeonOptions({ + this.input, + this.dartOut, + this.dartTestOut, + this.objcHeaderOut, + this.objcSourceOut, + this.objcOptions, + this.javaOut, + this.javaOptions, + this.swiftOut, + this.swiftOptions, + this.kotlinOut, + this.kotlinOptions, + this.cppHeaderOut, + this.cppSourceOut, + this.cppOptions, + this.dartOptions, + this.copyrightHeader, + this.oneLanguage, + this.astOut, + this.debugGenerators, + this.basePath, + }); /// Path to the file which will be processed. final String? input; @@ -250,6 +252,9 @@ class PigeonOptions { /// True means print out line number of generators in comments at newlines. final bool? debugGenerators; + /// A base path to be prepended to all provided output paths. + final String? basePath; + /// Creates a [PigeonOptions] from a Map representation where: /// `x = PigeonOptions.fromMap(x.toMap())`. static PigeonOptions fromMap(Map map) { @@ -286,6 +291,7 @@ class PigeonOptions { oneLanguage: map['oneLanguage'] as bool?, astOut: map['astOut'] as String?, debugGenerators: map['debugGenerators'] as bool?, + basePath: map['basePath'] as String?, ); } @@ -313,6 +319,7 @@ class PigeonOptions { if (astOut != null) 'astOut': astOut!, if (oneLanguage != null) 'oneLanguage': oneLanguage!, if (debugGenerators != null) 'debugGenerators': debugGenerators!, + if (basePath != null) 'basePath': basePath!, }; return result; } @@ -353,7 +360,7 @@ Iterable _lineReader(String path) sync* { } } -IOSink? _openSink(String? output) { +IOSink? _openSink(String? output, {String basePath = ''}) { if (output == null) { return null; } @@ -362,7 +369,7 @@ IOSink? _openSink(String? output) { if (output == 'stdout') { sink = stdout; } else { - file = File(output); + file = File(path.posix.join(basePath, output)); sink = file.openWrite(); } return sink; @@ -394,14 +401,19 @@ abstract class GeneratorAdapter { } DartOptions _dartOptionsWithCopyrightHeader( - DartOptions? dartOptions, String? copyrightHeader, - {String? dartOutPath, String? testOutPath}) { + DartOptions? dartOptions, + String? copyrightHeader, { + String? dartOutPath, + String? testOutPath, + String basePath = '', +}) { dartOptions = dartOptions ?? const DartOptions(); return dartOptions.merge(DartOptions( sourceOutPath: dartOutPath, testOutPath: testOutPath, - copyrightHeader: - copyrightHeader != null ? _lineReader(copyrightHeader) : null)); + copyrightHeader: copyrightHeader != null + ? _lineReader(path.posix.join(basePath, copyrightHeader)) + : null)); } /// A [GeneratorAdapter] that generates the AST. @@ -420,7 +432,7 @@ class AstGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType _) => - _openSink(options.astOut); + _openSink(options.astOut, basePath: options.basePath ?? ''); @override List validate(PigeonOptions options, Root root) => []; @@ -438,14 +450,17 @@ class DartGeneratorAdapter implements GeneratorAdapter { void generate( StringSink sink, PigeonOptions options, Root root, FileType fileType) { final DartOptions dartOptionsWithHeader = _dartOptionsWithCopyrightHeader( - options.dartOptions, options.copyrightHeader); + options.dartOptions, + options.copyrightHeader, + basePath: options.basePath ?? '', + ); const DartGenerator generator = DartGenerator(); generator.generate(dartOptionsWithHeader, root, sink); } @override IOSink? shouldGenerate(PigeonOptions options, FileType _) => - _openSink(options.dartOut); + _openSink(options.dartOut, basePath: options.basePath ?? ''); @override List validate(PigeonOptions options, Root root) => []; @@ -467,6 +482,7 @@ class DartTestGeneratorAdapter implements GeneratorAdapter { options.copyrightHeader, dartOutPath: options.dartOut, testOutPath: options.dartTestOut, + basePath: options.basePath ?? '', ); const DartGenerator testGenerator = DartGenerator(); testGenerator.generateTest(dartOptionsWithHeader, root, sink); @@ -475,7 +491,7 @@ class DartTestGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType _) { if (options.dartTestOut != null) { - return _openSink(options.dartTestOut); + return _openSink(options.dartTestOut, basePath: options.basePath ?? ''); } else { return null; } @@ -500,7 +516,8 @@ class ObjcGeneratorAdapter implements GeneratorAdapter { final ObjcOptions objcOptions = options.objcOptions ?? const ObjcOptions(); final ObjcOptions objcOptionsWithHeader = objcOptions.merge(ObjcOptions( copyrightHeader: options.copyrightHeader != null - ? _lineReader(options.copyrightHeader!) + ? _lineReader( + path.posix.join(options.basePath ?? '', options.copyrightHeader)) : null, )); final OutputFileOptions outputFileOptions = @@ -513,9 +530,9 @@ class ObjcGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType fileType) { if (fileType == FileType.source) { - return _openSink(options.objcSourceOut); + return _openSink(options.objcSourceOut, basePath: options.basePath ?? ''); } else { - return _openSink(options.objcHeaderOut); + return _openSink(options.objcHeaderOut, basePath: options.basePath ?? ''); } } @@ -539,7 +556,8 @@ class JavaGeneratorAdapter implements GeneratorAdapter { className: javaOptions.className ?? path.basenameWithoutExtension(options.javaOut!), copyrightHeader: options.copyrightHeader != null - ? _lineReader(options.copyrightHeader!) + ? _lineReader(path.posix + .join(options.basePath ?? '', options.copyrightHeader)) : null)); const JavaGenerator generator = JavaGenerator(); generator.generate(javaOptions, root, sink); @@ -547,7 +565,7 @@ class JavaGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType _) => - _openSink(options.javaOut); + _openSink(options.javaOut, basePath: options.basePath ?? ''); @override List validate(PigeonOptions options, Root root) => []; @@ -567,7 +585,8 @@ class SwiftGeneratorAdapter implements GeneratorAdapter { SwiftOptions swiftOptions = options.swiftOptions ?? const SwiftOptions(); swiftOptions = swiftOptions.merge(SwiftOptions( copyrightHeader: options.copyrightHeader != null - ? _lineReader(options.copyrightHeader!) + ? _lineReader(path.posix + .join(options.basePath ?? '', options.copyrightHeader)) : null)); const SwiftGenerator generator = SwiftGenerator(); generator.generate(swiftOptions, root, sink); @@ -575,7 +594,7 @@ class SwiftGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType _) => - _openSink(options.swiftOut); + _openSink(options.swiftOut, basePath: options.basePath ?? ''); @override List validate(PigeonOptions options, Root root) => []; @@ -596,7 +615,8 @@ class CppGeneratorAdapter implements GeneratorAdapter { final CppOptions cppOptions = options.cppOptions ?? const CppOptions(); final CppOptions cppOptionsWithHeader = cppOptions.merge(CppOptions( copyrightHeader: options.copyrightHeader != null - ? _lineReader(options.copyrightHeader!) + ? _lineReader( + path.posix.join(options.basePath ?? '', options.copyrightHeader)) : null, )); final OutputFileOptions outputFileOptions = @@ -609,9 +629,9 @@ class CppGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType fileType) { if (fileType == FileType.source) { - return _openSink(options.cppSourceOut); + return _openSink(options.cppSourceOut, basePath: options.basePath ?? ''); } else { - return _openSink(options.cppHeaderOut); + return _openSink(options.cppHeaderOut, basePath: options.basePath ?? ''); } } @@ -635,7 +655,8 @@ class KotlinGeneratorAdapter implements GeneratorAdapter { kotlinOptions = kotlinOptions.merge(KotlinOptions( errorClassName: kotlinOptions.errorClassName ?? 'FlutterError', copyrightHeader: options.copyrightHeader != null - ? _lineReader(options.copyrightHeader!) + ? _lineReader(path.posix + .join(options.basePath ?? '', options.copyrightHeader)) : null)); const KotlinGenerator generator = KotlinGenerator(); generator.generate(kotlinOptions, root, sink); @@ -643,7 +664,7 @@ class KotlinGeneratorAdapter implements GeneratorAdapter { @override IOSink? shouldGenerate(PigeonOptions options, FileType _) => - _openSink(options.kotlinOut); + _openSink(options.kotlinOut, basePath: options.basePath ?? ''); @override List validate(PigeonOptions options, Root root) => []; @@ -897,7 +918,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } else { _errors.add(Error( message: - 'unrecongized expression type ${expression.runtimeType} $expression', + 'unrecognized expression type ${expression.runtimeType} $expression', lineNumber: _calculateLineNumber(source, expression.offset), )); return 0; @@ -1356,14 +1377,17 @@ ${_argParser.usage}'''; help: 'Path to generated AST debugging info. (Warning: format subject to change)') ..addFlag('debug_generators', + help: 'Print the line number of the generator in comments at newlines.') + ..addOption('base_path', help: - 'Print the line number of the generator in comments at newlines.'); + 'A base path to be prefixed to all outputs and copyright header path. Generally used for testing', + hide: true); /// Convert command-line arguments to [PigeonOptions]. static PigeonOptions parseArgs(List args) { // Note: This function shouldn't perform any logic, just translate the args // to PigeonOptions. Synthesized values inside of the PigeonOption should - // get set in the `run` function to accomodate users that are using the + // get set in the `run` function to accommodate users that are using the // `configurePigeon` function. final ArgResults results = _argParser.parse(args); @@ -1396,6 +1420,7 @@ ${_argParser.usage}'''; oneLanguage: results['one_language'] as bool?, astOut: results['ast_out'] as String?, debugGenerators: results['debug_generators'] as bool?, + basePath: results['base_path'] as String?, ); return opts; } diff --git a/packages/pigeon/pigeons/README.md b/packages/pigeon/pigeons/README.md index bdf5ca7c39bf..9f263457a4a7 100644 --- a/packages/pigeon/pigeons/README.md +++ b/packages/pigeon/pigeons/README.md @@ -1,5 +1,5 @@ This directory contains Pigeon API definitions used to generate code for tests. -Please do not add new files to this directory unless absolutely neccessary; +Please do not add new files to this directory unless absolutely necessary; most additions should go into core_tests.dart. See https://github.com/flutter/flutter/issues/115168 for context. diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart index ac9fd7768dc6..15de1ffbbbc6 100644 --- a/packages/pigeon/pigeons/core_tests.dart +++ b/packages/pigeon/pigeons/core_tests.dart @@ -92,7 +92,7 @@ class AllNullableTypesWrapper { /// platform_test integration tests. @HostApi() abstract class HostIntegrationCoreApi { - // ========== Syncronous method tests ========== + // ========== Synchronous method tests ========== /// A no-op function taking no arguments and returning no value, to sanity /// test basic calling. @@ -152,7 +152,7 @@ abstract class HostIntegrationCoreApi { @SwiftFunction('echo(_:)') Map echoMap(Map aMap); - // ========== Syncronous nullable method tests ========== + // ========== Synchronous nullable method tests ========== /// Returns the passed object, to test serialization and deserialization. @ObjCSelector('echoAllNullableTypes:') @@ -217,7 +217,7 @@ abstract class HostIntegrationCoreApi { @SwiftFunction('echoNullable(_:)') Map? echoNullableMap(Map? aNullableMap); - // ========== Asyncronous method tests ========== + // ========== Asynchronous method tests ========== /// A no-op function taking no arguments and returning no value, to sanity /// test basic asynchronous calling. @@ -342,7 +342,7 @@ abstract class HostIntegrationCoreApi { /// Returns the passed map, to test serialization and deserialization asynchronously. @async @ObjCSelector('echoAsyncNullableMap:') - @SwiftFunction('echAsyncoNullable(_:)') + @SwiftFunction('echoAsyncNullable(_:)') Map? echoAsyncNullableMap(Map? aMap); // ========== Flutter API test wrappers ========== @@ -609,7 +609,7 @@ abstract class FlutterSmallApi { /// A data class containing a List, used in unit tests. // TODO(stuartmorgan): Evaluate whether these unit tests are still useful; see -// TODOs above about restructring. +// TODOs above about restructuring. class TestMessage { // ignore: always_specify_types, strict_raw_type List? testList; diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj index ec7df0395828..393001eb4967 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/project.pbxproj @@ -266,7 +266,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33AA165E291EB8B600ECBEEB = { diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e283d743f3bd..28b953fd1269 100644 --- a/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ ) -> Void) /// Returns the passed map, to test serialization and deserialization asynchronously. - func echAsyncoNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) + func echoAsyncNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) func callFlutterNoop(completion: @escaping (Result) -> Void) func callFlutterThrowError(completion: @escaping (Result) -> Void) func callFlutterThrowErrorFromVoid(completion: @escaping (Result) -> Void) @@ -1177,7 +1177,7 @@ class HostIntegrationCoreApiSetup { echoAsyncNullableMapChannel.setMessageHandler { message, reply in let args = message as! [Any?] let aMapArg: [String?: Any?]? = nilOrValue(args[0]) - api.echAsyncoNullable(aMapArg) { result in + api.echoAsyncNullable(aMapArg) { result in switch result { case .success(let res): reply(wrapResult(res)) diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift index 36ecbfd43287..368dbdeb64fd 100644 --- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift @@ -210,7 +210,7 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(aList)) } - func echAsyncoNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) { + func echoAsyncNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) { completion(.success(aMap)) } diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift index 2590ee6aa87c..4c1dfa08b169 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift @@ -375,7 +375,7 @@ protocol HostIntegrationCoreApi { /// Returns the passed list, to test serialization and deserialization asynchronously. func echoAsyncNullable(_ aList: [Any?]?, completion: @escaping (Result<[Any?]?, Error>) -> Void) /// Returns the passed map, to test serialization and deserialization asynchronously. - func echAsyncoNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) + func echoAsyncNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) func callFlutterNoop(completion: @escaping (Result) -> Void) func callFlutterThrowError(completion: @escaping (Result) -> Void) func callFlutterThrowErrorFromVoid(completion: @escaping (Result) -> Void) @@ -1177,7 +1177,7 @@ class HostIntegrationCoreApiSetup { echoAsyncNullableMapChannel.setMessageHandler { message, reply in let args = message as! [Any?] let aMapArg: [String?: Any?]? = nilOrValue(args[0]) - api.echAsyncoNullable(aMapArg) { result in + api.echoAsyncNullable(aMapArg) { result in switch result { case .success(let res): reply(wrapResult(res)) diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift index 04bda07e95fd..51a68c370e91 100644 --- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift +++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift @@ -209,7 +209,7 @@ public class TestPlugin: NSObject, FlutterPlugin, HostIntegrationCoreApi { completion(.success(aList)) } - func echAsyncoNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) { + func echoAsyncNullable(_ aMap: [String?: Any?]?, completion: @escaping (Result<[String?: Any?]?, Error>) -> Void) { completion(.success(aMap)) } diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 3c4e0776eae9..82531c4a8871 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. repository: https://github.com/flutter/packages/tree/main/packages/pigeon issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon -version: 10.1.0 # This must match the version in lib/generator_tools.dart +version: 10.1.1 # This must match the version in lib/generator_tools.dart environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 47c098d960e4..7e7cc216d8c7 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -145,6 +145,12 @@ void main() { expect(opts.astOut, equals('stdout')); }); + test('parse args - base_path', () { + final PigeonOptions opts = + Pigeon.parseArgs(['--base_path', './foo/']); + expect(opts.basePath, equals('./foo/')); + }); + test('simple parse api', () { const String code = ''' class Input1 { @@ -399,7 +405,7 @@ abstract class NestorApi { expect(results.copyrightHeader, 'foobar.txt'); }); - test('Dart generater copyright flag', () { + test('Dart generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions(copyrightHeader: './copyright_header.txt'); @@ -409,7 +415,7 @@ abstract class NestorApi { expect(buffer.toString(), startsWith('// Copyright 2013')); }); - test('Java generater copyright flag', () { + test('Java generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions( javaOut: 'Foo.java', copyrightHeader: './copyright_header.txt'); @@ -419,7 +425,7 @@ abstract class NestorApi { expect(buffer.toString(), startsWith('// Copyright 2013')); }); - test('Objc header generater copyright flag', () { + test('Objc header generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions(copyrightHeader: './copyright_header.txt'); @@ -430,7 +436,7 @@ abstract class NestorApi { expect(buffer.toString(), startsWith('// Copyright 2013')); }); - test('Objc source generater copyright flag', () { + test('Objc source generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions(copyrightHeader: './copyright_header.txt'); @@ -441,7 +447,7 @@ abstract class NestorApi { expect(buffer.toString(), startsWith('// Copyright 2013')); }); - test('Swift generater copyright flag', () { + test('Swift generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions( swiftOut: 'Foo.swift', copyrightHeader: './copyright_header.txt'); @@ -451,7 +457,7 @@ abstract class NestorApi { expect(buffer.toString(), startsWith('// Copyright 2013')); }); - test('C++ header generater copyright flag', () { + test('C++ header generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions( cppHeaderOut: 'Foo.h', copyrightHeader: './copyright_header.txt'); @@ -461,7 +467,7 @@ abstract class NestorApi { expect(buffer.toString(), startsWith('// Copyright 2013')); }); - test('C++ source generater copyright flag', () { + test('C++ source generator copyright flag', () { final Root root = Root(apis: [], classes: [], enums: []); const PigeonOptions options = PigeonOptions(copyrightHeader: './copyright_header.txt'); diff --git a/packages/pigeon/tool/generate.dart b/packages/pigeon/tool/generate.dart index 946a69c4c676..02e28ad81c14 100644 --- a/packages/pigeon/tool/generate.dart +++ b/packages/pigeon/tool/generate.dart @@ -38,7 +38,7 @@ ${parser.usage}'''); final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath())); print('Generating platform_test/ output...'); - final int generateExitCode = await generatePigeons(baseDir: baseDir); + final int generateExitCode = await generateTestPigeons(baseDir: baseDir); if (generateExitCode == 0) { print('Generation complete!'); } else { diff --git a/packages/pigeon/tool/run_tests.dart b/packages/pigeon/tool/run_tests.dart index b68117fb950a..4d1c4fa2cc9e 100644 --- a/packages/pigeon/tool/run_tests.dart +++ b/packages/pigeon/tool/run_tests.dart @@ -38,8 +38,8 @@ Future _validateGeneratedTestFiles() async { final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot); print('Validating generated files:'); - print(' Generating output...'); - final int generateExitCode = await generatePigeons(baseDir: baseDir); + print(' Generating test output...'); + final int generateExitCode = await generateTestPigeons(baseDir: baseDir); if (generateExitCode != 0) { print('Generation failed; see above for errors.'); exit(generateExitCode); @@ -82,6 +82,63 @@ Future _validateGeneratedTestFiles() async { exit(1); } +Future _validateGeneratedExampleFiles() async { + final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath())); + final String repositoryRoot = p.dirname(p.dirname(baseDir)); + final String relativePigeonPath = p.relative(baseDir, from: repositoryRoot); + + print('Validating generated files:'); + print(' Generating example output...'); + + final int generateExitCode = await runPigeon( + input: './example/app/pigeons/messages.dart', + basePath: './example/app', + ); + + if (generateExitCode != 0) { + print('Generation failed; see above for errors.'); + exit(generateExitCode); + } + + print(' Formatting output...'); + final int formatExitCode = + await formatAllFiles(repositoryRoot: repositoryRoot); + if (formatExitCode != 0) { + print('Formatting failed; see above for errors.'); + exit(formatExitCode); + } + + print(' Checking for changes...'); + final List modifiedFiles = await _modifiedFiles( + repositoryRoot: repositoryRoot, relativePigeonPath: relativePigeonPath); + + if (modifiedFiles.isEmpty) { + return; + } + + print( + 'Either messages.dart and messages_test.dart have non-matching definitions or'); + print('the following files are not updated, or not formatted correctly:'); + modifiedFiles.map((String line) => ' $line').forEach(print); + + print('\nTo fix run "dart run tool/generate.dart --format" from the pigeon/ ' + 'directory, or apply the diff with the command below.\n'); + + final ProcessResult diffResult = await Process.run( + 'git', + ['diff', relativePigeonPath], + workingDirectory: repositoryRoot, + ); + if (diffResult.exitCode != 0) { + print('Unable to determine diff.'); + exit(1); + } + print('patch -p1 <> _modifiedFiles( {required String repositoryRoot, required String relativePigeonPath}) async { @@ -165,6 +222,7 @@ Future main(List args) async { print('Skipping generated file validation on stable.'); } else { await _validateGeneratedTestFiles(); + await _validateGeneratedExampleFiles(); } } diff --git a/packages/pigeon/tool/shared/generation.dart b/packages/pigeon/tool/shared/generation.dart index 7d1b5c9d5a88..ee54f2f263d4 100644 --- a/packages/pigeon/tool/shared/generation.dart +++ b/packages/pigeon/tool/shared/generation.dart @@ -45,7 +45,7 @@ String _javaFilenameForName(String inputName) { return specialCases[inputName] ?? _snakeToPascalCase(inputName); } -Future generatePigeons({required String baseDir}) async { +Future generateTestPigeons({required String baseDir}) async { // TODO(stuartmorgan): Make this dynamic rather than hard-coded. Or eliminate // it entirely; see https://github.com/flutter/flutter/issues/115169. const List inputs = [ @@ -173,7 +173,10 @@ Future runPigeon({ String? javaPackage, String? objcHeaderOut, String? objcSourceOut, + String objcPrefix = '', bool suppressVersion = false, + String copyrightHeader = './copyright_header.txt', + String? basePath, }) async { // Temporarily suppress the version output via the global flag if requested. // This is done because having the version in all the generated test output @@ -189,7 +192,7 @@ Future runPigeon({ } final int result = await Pigeon.runWithOptions(PigeonOptions( input: input, - copyrightHeader: './copyright_header.txt', + copyrightHeader: copyrightHeader, dartOut: dartOut, dartTestOut: dartTestOut, dartOptions: const DartOptions(), @@ -203,9 +206,10 @@ Future runPigeon({ package: kotlinPackage, errorClassName: kotlinErrorClassName), objcHeaderOut: objcHeaderOut, objcSourceOut: objcSourceOut, - objcOptions: const ObjcOptions(), + objcOptions: ObjcOptions(prefix: objcPrefix), swiftOut: swiftOut, swiftOptions: const SwiftOptions(), + basePath: basePath, )); includeVersionInGeneratedWarning = originalWarningSetting; return result; @@ -213,7 +217,7 @@ Future runPigeon({ /// Runs the repository tooling's format command on this package. /// -/// This is intended for formatting generated autoput, but since there's no +/// This is intended for formatting generated output, but since there's no /// way to filter to specific files in with the repo tooling it runs over the /// entire package. Future formatAllFiles({required String repositoryRoot}) { diff --git a/packages/pigeon/tool/shared/test_runner.dart b/packages/pigeon/tool/shared/test_runner.dart index abbf5edbd864..ba663946b83e 100644 --- a/packages/pigeon/tool/shared/test_runner.dart +++ b/packages/pigeon/tool/shared/test_runner.dart @@ -19,7 +19,7 @@ Future runTests(List testsToRun) async { // tests being run, as not all of them need these files. final String baseDir = p.dirname(p.dirname(Platform.script.toFilePath())); print('# Generating platform_test/ output...'); - final int generateExitCode = await generatePigeons(baseDir: baseDir); + final int generateExitCode = await generateTestPigeons(baseDir: baseDir); if (generateExitCode == 0) { print('Generation complete!'); } else {