Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
## 12.0.0

* Adds error handling on Flutter API methods.
* **Breaking Change** [kotlin] Flutter API methods now return `Result<return-type>`.
* **Breaking Change** [swift] Flutter API methods now return `Result<return-type, FlutterError>`.
* **Breaking Change** [java] Removes `Reply` class from all method returns and replaces it with `Result`.
* Changes required: Replace all `Reply` callbacks with `Result` classes that contain both `success` and `failure` methods.
* **Breaking Change** [java] Adds `NullableResult` class for all nullable method returns.
* Changes required: Any method that returns a nullable type will need to be updated to return `NullableResult` rather than `Result`.
* **Breaking Change** [java] Renames Host API `setup` method to `setUp`.
* **Breaking Change** [objc] Boxes all enum returns to allow for `nil` response on error.
* **Breaking Change** [objc] Renames `<api>Setup` to `SetUp<api>`.

## 11.0.1

* Adds pub topics to package metadata.

## 11.0.0

* Adds primitive enum support.
* Fixes Objective-C nullable enums.
* **Breaking Change** Changes all nullable enums in Objective-C to be wrapped in custom classes.
* **Breaking Change** Changes all enums names in Objective-C to have class prefix.
* [objc] Fixes nullable enums.
* **Breaking Change** [objc] Changes all nullable enums to be boxed in custom classes.
* **Breaking Change** [objc] Changes all enums names to have class prefix.
* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.

## 10.1.6
Expand All @@ -16,7 +29,7 @@

## 10.1.5

* Fixes import in generated Dart test output when overriding package name.
* [dart] Fixes import in generated test output when overriding package name.

## 10.1.4

Expand Down Expand Up @@ -45,11 +58,11 @@

## 10.0.0

* [swift] Avoids using `Any` to represent `Optional` in Swift.
* [swift] Avoids using `Any` to represent `Optional`.
* [swift] **Breaking Change** A raw `List` (without generic type argument) in Dart will be
translated into `[Any?]` (rather than `[Any]`) in Swift.
translated into `[Any?]` (rather than `[Any]`).
* [swift] **Breaking Change** A raw `Map` (without generic type argument) in Dart will be
translated into `[AnyHashable:Any?]` (rather than `[AnyHashable:Any]`) in Swift.
translated into `[AnyHashable:Any?]` (rather than `[AnyHashable:Any]`).
* Adds an example application that uses Pigeon directly, rather than in a plugin.

## 9.2.5
Expand Down Expand Up @@ -275,7 +288,7 @@

## 4.2.10

* Changes generated Java enum field to be final.
* [java] Changes generated enum field to be final.

## 4.2.9

Expand Down Expand Up @@ -457,11 +470,11 @@

## 2.0.3

* Makes the generated Java Builder class final.
* [java] Makes the generated Builder class final.

## 2.0.2

* Fixes Java crash for nullable nested type.
* [java] Fixes crash for nullable nested type.

## 2.0.1

Expand Down Expand Up @@ -583,8 +596,8 @@
* [generators] Moved Pigeon to using a custom codec which allows collection
types to contain custom classes.
* [java] Fixed NPE in Java generated code for nested types.
* [objc] **BREAKING CHANGE:** logic for generating Objective-C selectors has
changed. `void add(Input value)` will now translate to
* [objc] **BREAKING CHANGE:** logic for generating selectors has changed.
`void add(Input value)` will now translate to
`-(void)addValue:(Input*)value`, methods with no arguments will translate to
`...WithError:` or `...WithCompletion:`.
* [objc] Added `@ObjCSelector` for specifying custom objc selectors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/** Generated class from Pigeon. */
Expand Down Expand Up @@ -180,10 +181,20 @@ ArrayList<Object> toList() {
}
}

/** Asynchronous error handling return type for non-nullable API method returns. */
public interface Result<T> {
@SuppressWarnings("UnknownNullness")
void success(T result);
/** Success case callback method for handling returns. */
void success(@NonNull T result);

/** Failure case callback method for handling errors. */
void error(@NonNull Throwable error);
}
/** Asynchronous error handling return type for nullable API method returns. */
public interface NullableResult<T> {
/** Success case callback method for handling returns. */
void success(@Nullable T result);

/** Failure case callback method for handling errors. */
void error(@NonNull Throwable error);
}

Expand Down Expand Up @@ -229,7 +240,7 @@ public interface ExampleHostApi {
return ExampleHostApiCodec.INSTANCE;
}
/** Sets up an instance of `ExampleHostApi` to handle messages through the `binaryMessenger`. */
static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable ExampleHostApi api) {
static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable ExampleHostApi api) {
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
Expand Down Expand Up @@ -324,16 +335,12 @@ public MessageFlutterApi(@NonNull BinaryMessenger argBinaryMessenger) {
}

/** Public interface for sending reply. */
@SuppressWarnings("UnknownNullness")
public interface Reply<T> {
void reply(T reply);
}
/** The codec used by MessageFlutterApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}

public void flutterMethod(@Nullable String aStringArg, @NonNull Reply<String> callback) {
public void flutterMethod(@Nullable String aStringArg, @NonNull Result<String> result) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
Expand All @@ -342,9 +349,30 @@ public void flutterMethod(@Nullable String aStringArg, @NonNull Reply<String> ca
channel.send(
new ArrayList<Object>(Collections.singletonList(aStringArg)),
channelReply -> {
@SuppressWarnings("ConstantConditions")
String output = (String) channelReply;
callback.reply(output);
if (channelReply instanceof List) {
List<Object> listReply = (List<Object>) channelReply;
if (listReply.size() > 1) {
result.error(
new FlutterError(
(String) listReply.get(0),
(String) listReply.get(1),
(String) listReply.get(2)));
} else if (listReply.get(0) == null) {
result.error(
new FlutterError(
"null-error",
"Flutter api returned null value for non-null return value.",
""));
} else {
@SuppressWarnings("ConstantConditions")
String output = (String) listReply.get(0);
result.success(output);
}
} else {
result.error(
new FlutterError(
"channel-error", "Unable to establish connection on channel.", ""));
}
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,21 @@ class MessageFlutterApi(private val binaryMessenger: BinaryMessenger) {
StandardMessageCodec()
}
}
fun flutterMethod(aStringArg: String?, callback: (String) -> Unit) {
fun flutterMethod(aStringArg: String?, callback: (Result<String>) -> Unit) {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod", codec)
channel.send(listOf(aStringArg)) {
val result = it as String
callback(result)
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
} else if (it[0] == null) {
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")));
} else {
val output = it[0] as String
callback(Result.success(output));
}
} else {
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
}
}
}
}
19 changes: 16 additions & 3 deletions packages/pigeon/example/app/ios/Runner/Messages.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,24 @@ class MessageFlutterApi {
init(binaryMessenger: FlutterBinaryMessenger){
self.binaryMessenger = binaryMessenger
}
func flutterMethod(aString aStringArg: String?, completion: @escaping (String) -> Void) {
func flutterMethod(aString aStringArg: String?, completion: @escaping (Result<String, FlutterError>) -> Void) {
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod", binaryMessenger: binaryMessenger)
channel.sendMessage([aStringArg] as [Any?]) { response in
let result = response as! String
completion(result)
guard let listResponse = response as? [Any?] else {
completion(.failure(FlutterError(code: "channel-error", message: "Unable to establish connection on channel.", details: "")))
return
}
if (listResponse.count > 1) {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(FlutterError(code: code, message: message, details: details)));
} else if (listResponse[0] == nil) {
completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
} else {
let result = listResponse[0] as! String
completion(.success(result))
}
}
}
}
22 changes: 20 additions & 2 deletions packages/pigeon/example/app/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

List<Object?> wrapResponse(
{Object? result, PlatformException? error, bool empty = false}) {
if (empty) {
return <Object?>[];
}
if (error == null) {
return <Object?>[result];
}
return <Object?>[error.code, error.message, error.details];
}

enum Code {
one,
two,
Expand Down Expand Up @@ -188,8 +199,15 @@ abstract class MessageFlutterApi {
'Argument for dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod was null.');
final List<Object?> args = (message as List<Object?>?)!;
final String? arg_aString = (args[0] as String?);
final String output = api.flutterMethod(arg_aString);
return output;
try {
final String output = api.flutterMethod(arg_aString);
return wrapResponse(result: output);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()));
}
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/example/app/macos/Runner/messages.g.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ NSObject<FlutterMessageCodec> *PGNExampleHostApiGetCodec(void);
completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;
@end

extern void PGNExampleHostApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
extern void SetUpPGNExampleHostApi(id<FlutterBinaryMessenger> binaryMessenger,
NSObject<PGNExampleHostApi> *_Nullable api);

/// The codec used by PGNMessageFlutterApi.
Expand Down
21 changes: 17 additions & 4 deletions packages/pigeon/example/app/macos/Runner/messages.g.m
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data {
return sSharedObject;
}

void PGNExampleHostApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
void SetUpPGNExampleHostApi(id<FlutterBinaryMessenger> binaryMessenger,
NSObject<PGNExampleHostApi> *api) {
{
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
Expand Down Expand Up @@ -220,9 +220,22 @@ - (void)flutterMethodAString:(nullable NSString *)arg_aString
binaryMessenger:self.binaryMessenger
codec:PGNMessageFlutterApiGetCodec()];
[channel sendMessage:@[ arg_aString ?: [NSNull null] ]
reply:^(id reply) {
NSString *output = reply;
completion(output, nil);
reply:^(NSArray<id> *reply) {
if (reply != nil) {
if (reply.count > 1) {
completion(nil, [FlutterError errorWithCode:reply[0]
message:reply[1]
details:reply[2]]);
} else {
NSString *output = reply[0] == [NSNull null] ? nil : reply[0];
completion(output, nil);
}
} else {
completion(nil, [FlutterError
errorWithCode:@"channel-error"
message:@"Unable to establish connection on channel."
details:@""]);
}
}];
}
@end
32 changes: 23 additions & 9 deletions packages/pigeon/example/app/windows/runner/messages.g.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,17 +271,31 @@ void MessageFlutterApi::FlutterMethod(
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<EncodableValue> response =
GetCodec().DecodeMessage(reply, reply_size);
const auto& encodable_return_value = *response;
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<EncodableValue> response =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could do the same (extract the entire implementation of the lambda to a static helper method) if we make it templated in the return value type and then add template specializations for special cases like enums. But we can leave that to a follow-up.

GetCodec().DecodeMessage(reply, reply_size);
const auto& encodable_return_value = *response;
const auto* list_return_value =
std::get_if<EncodableList>(&encodable_return_value);
if (list_return_value) {
if (list_return_value->size() > 1) {
on_error(FlutterError(std::get<std::string>(list_return_value->at(0)),
std::get<std::string>(list_return_value->at(1)),
list_return_value->at(2)));
} else {
const auto& return_value =
std::get<std::string>(encodable_return_value);
std::get<std::string>(list_return_value->at(0));
on_success(return_value);
});
}
} else {
on_error(FlutterError("channel-error",
"Unable to establish connection on channel.",
EncodableValue("")));
}
});
}

} // namespace pigeon_example
Loading