Skip to content

Commit

Permalink
[pigeon] FlutterApi error handling (#5008)
Browse files Browse the repository at this point in the history
* Adds error handling on Flutter Api methods.
* **Breaking Change** Removes `Reply` class from all Java method returns.
* **Breaking Change** Adds `NullableResult` class for all nullable Java method returns.
* **Breaking Change** Nests all enum returns in Objective-c to allow for `nil` response on error.
* **Breaking Change** Renames `Setup` to `SetUp` in Objective-c.

fixes flutter/flutter#118243
fixes flutter/flutter#118245
fixes flutter/flutter#124268
fixes flutter/flutter#135176
  • Loading branch information
tarrinneal authored Oct 4, 2023
1 parent a66be0e commit d654f75
Show file tree
Hide file tree
Showing 80 changed files with 4,865 additions and 1,719 deletions.
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 =
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

0 comments on commit d654f75

Please sign in to comment.