Skip to content

Commit d654f75

Browse files
authored
[pigeon] FlutterApi error handling (flutter#5008)
* 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#118243 fixes flutter#118245 fixes flutter#124268 fixes flutter#135176
1 parent a66be0e commit d654f75

File tree

80 files changed

+4865
-1719
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+4865
-1719
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1+
## 12.0.0
2+
3+
* Adds error handling on Flutter API methods.
4+
* **Breaking Change** [kotlin] Flutter API methods now return `Result<return-type>`.
5+
* **Breaking Change** [swift] Flutter API methods now return `Result<return-type, FlutterError>`.
6+
* **Breaking Change** [java] Removes `Reply` class from all method returns and replaces it with `Result`.
7+
* Changes required: Replace all `Reply` callbacks with `Result` classes that contain both `success` and `failure` methods.
8+
* **Breaking Change** [java] Adds `NullableResult` class for all nullable method returns.
9+
* Changes required: Any method that returns a nullable type will need to be updated to return `NullableResult` rather than `Result`.
10+
* **Breaking Change** [java] Renames Host API `setup` method to `setUp`.
11+
* **Breaking Change** [objc] Boxes all enum returns to allow for `nil` response on error.
12+
* **Breaking Change** [objc] Renames `<api>Setup` to `SetUp<api>`.
13+
114
## 11.0.1
215

316
* Adds pub topics to package metadata.
417

518
## 11.0.0
619

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

1326
## 10.1.6
@@ -16,7 +29,7 @@
1629

1730
## 10.1.5
1831

19-
* Fixes import in generated Dart test output when overriding package name.
32+
* [dart] Fixes import in generated test output when overriding package name.
2033

2134
## 10.1.4
2235

@@ -45,11 +58,11 @@
4558

4659
## 10.0.0
4760

48-
* [swift] Avoids using `Any` to represent `Optional` in Swift.
61+
* [swift] Avoids using `Any` to represent `Optional`.
4962
* [swift] **Breaking Change** A raw `List` (without generic type argument) in Dart will be
50-
translated into `[Any?]` (rather than `[Any]`) in Swift.
63+
translated into `[Any?]` (rather than `[Any]`).
5164
* [swift] **Breaking Change** A raw `Map` (without generic type argument) in Dart will be
52-
translated into `[AnyHashable:Any?]` (rather than `[AnyHashable:Any]`) in Swift.
65+
translated into `[AnyHashable:Any?]` (rather than `[AnyHashable:Any]`).
5366
* Adds an example application that uses Pigeon directly, rather than in a plugin.
5467

5568
## 9.2.5
@@ -275,7 +288,7 @@
275288

276289
## 4.2.10
277290

278-
* Changes generated Java enum field to be final.
291+
* [java] Changes generated enum field to be final.
279292

280293
## 4.2.9
281294

@@ -457,11 +470,11 @@
457470

458471
## 2.0.3
459472

460-
* Makes the generated Java Builder class final.
473+
* [java] Makes the generated Builder class final.
461474

462475
## 2.0.2
463476

464-
* Fixes Java crash for nullable nested type.
477+
* [java] Fixes crash for nullable nested type.
465478

466479
## 2.0.1
467480

@@ -583,8 +596,8 @@
583596
* [generators] Moved Pigeon to using a custom codec which allows collection
584597
types to contain custom classes.
585598
* [java] Fixed NPE in Java generated code for nested types.
586-
* [objc] **BREAKING CHANGE:** logic for generating Objective-C selectors has
587-
changed. `void add(Input value)` will now translate to
599+
* [objc] **BREAKING CHANGE:** logic for generating selectors has changed.
600+
`void add(Input value)` will now translate to
588601
`-(void)addValue:(Input*)value`, methods with no arguments will translate to
589602
`...WithError:` or `...WithCompletion:`.
590603
* [objc] Added `@ObjCSelector` for specifying custom objc selectors.

packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.nio.ByteBuffer;
1616
import java.util.ArrayList;
1717
import java.util.Collections;
18+
import java.util.List;
1819
import java.util.Map;
1920

2021
/** Generated class from Pigeon. */
@@ -180,10 +181,20 @@ ArrayList<Object> toList() {
180181
}
181182
}
182183

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

189+
/** Failure case callback method for handling errors. */
190+
void error(@NonNull Throwable error);
191+
}
192+
/** Asynchronous error handling return type for nullable API method returns. */
193+
public interface NullableResult<T> {
194+
/** Success case callback method for handling returns. */
195+
void success(@Nullable T result);
196+
197+
/** Failure case callback method for handling errors. */
187198
void error(@NonNull Throwable error);
188199
}
189200

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

326337
/** Public interface for sending reply. */
327-
@SuppressWarnings("UnknownNullness")
328-
public interface Reply<T> {
329-
void reply(T reply);
330-
}
331338
/** The codec used by MessageFlutterApi. */
332339
static @NonNull MessageCodec<Object> getCodec() {
333340
return new StandardMessageCodec();
334341
}
335342

336-
public void flutterMethod(@Nullable String aStringArg, @NonNull Reply<String> callback) {
343+
public void flutterMethod(@Nullable String aStringArg, @NonNull Result<String> result) {
337344
BasicMessageChannel<Object> channel =
338345
new BasicMessageChannel<>(
339346
binaryMessenger,
@@ -342,9 +349,30 @@ public void flutterMethod(@Nullable String aStringArg, @NonNull Reply<String> ca
342349
channel.send(
343350
new ArrayList<Object>(Collections.singletonList(aStringArg)),
344351
channelReply -> {
345-
@SuppressWarnings("ConstantConditions")
346-
String output = (String) channelReply;
347-
callback.reply(output);
352+
if (channelReply instanceof List) {
353+
List<Object> listReply = (List<Object>) channelReply;
354+
if (listReply.size() > 1) {
355+
result.error(
356+
new FlutterError(
357+
(String) listReply.get(0),
358+
(String) listReply.get(1),
359+
(String) listReply.get(2)));
360+
} else if (listReply.get(0) == null) {
361+
result.error(
362+
new FlutterError(
363+
"null-error",
364+
"Flutter api returned null value for non-null return value.",
365+
""));
366+
} else {
367+
@SuppressWarnings("ConstantConditions")
368+
String output = (String) listReply.get(0);
369+
result.success(output);
370+
}
371+
} else {
372+
result.error(
373+
new FlutterError(
374+
"channel-error", "Unable to establish connection on channel.", ""));
375+
}
348376
});
349377
}
350378
}

packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,21 @@ class MessageFlutterApi(private val binaryMessenger: BinaryMessenger) {
188188
StandardMessageCodec()
189189
}
190190
}
191-
fun flutterMethod(aStringArg: String?, callback: (String) -> Unit) {
191+
fun flutterMethod(aStringArg: String?, callback: (Result<String>) -> Unit) {
192192
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod", codec)
193193
channel.send(listOf(aStringArg)) {
194-
val result = it as String
195-
callback(result)
194+
if (it is List<*>) {
195+
if (it.size > 1) {
196+
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)));
197+
} else if (it[0] == null) {
198+
callback(Result.failure(FlutterError("null-error", "Flutter api returned null value for non-null return value.", "")));
199+
} else {
200+
val output = it[0] as String
201+
callback(Result.success(output));
202+
}
203+
} else {
204+
callback(Result.failure(FlutterError("channel-error", "Unable to establish connection on channel.", "")));
205+
}
196206
}
197207
}
198208
}

packages/pigeon/example/app/ios/Runner/Messages.g.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,24 @@ class MessageFlutterApi {
179179
init(binaryMessenger: FlutterBinaryMessenger){
180180
self.binaryMessenger = binaryMessenger
181181
}
182-
func flutterMethod(aString aStringArg: String?, completion: @escaping (String) -> Void) {
182+
func flutterMethod(aString aStringArg: String?, completion: @escaping (Result<String, FlutterError>) -> Void) {
183183
let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod", binaryMessenger: binaryMessenger)
184184
channel.sendMessage([aStringArg] as [Any?]) { response in
185-
let result = response as! String
186-
completion(result)
185+
guard let listResponse = response as? [Any?] else {
186+
completion(.failure(FlutterError(code: "channel-error", message: "Unable to establish connection on channel.", details: "")))
187+
return
188+
}
189+
if (listResponse.count > 1) {
190+
let code: String = listResponse[0] as! String
191+
let message: String? = nilOrValue(listResponse[1])
192+
let details: String? = nilOrValue(listResponse[2])
193+
completion(.failure(FlutterError(code: code, message: message, details: details)));
194+
} else if (listResponse[0] == nil) {
195+
completion(.failure(FlutterError(code: "null-error", message: "Flutter api returned null value for non-null return value.", details: "")))
196+
} else {
197+
let result = listResponse[0] as! String
198+
completion(.success(result))
199+
}
187200
}
188201
}
189202
}

packages/pigeon/example/app/lib/src/messages.g.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
1111
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
1212
import 'package:flutter/services.dart';
1313

14+
List<Object?> wrapResponse(
15+
{Object? result, PlatformException? error, bool empty = false}) {
16+
if (empty) {
17+
return <Object?>[];
18+
}
19+
if (error == null) {
20+
return <Object?>[result];
21+
}
22+
return <Object?>[error.code, error.message, error.details];
23+
}
24+
1425
enum Code {
1526
one,
1627
two,
@@ -188,8 +199,15 @@ abstract class MessageFlutterApi {
188199
'Argument for dev.flutter.pigeon.pigeon_example_package.MessageFlutterApi.flutterMethod was null.');
189200
final List<Object?> args = (message as List<Object?>?)!;
190201
final String? arg_aString = (args[0] as String?);
191-
final String output = api.flutterMethod(arg_aString);
192-
return output;
202+
try {
203+
final String output = api.flutterMethod(arg_aString);
204+
return wrapResponse(result: output);
205+
} on PlatformException catch (e) {
206+
return wrapResponse(error: e);
207+
} catch (e) {
208+
return wrapResponse(
209+
error: PlatformException(code: 'error', message: e.toString()));
210+
}
193211
});
194212
}
195213
}

packages/pigeon/example/app/macos/Runner/messages.g.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ NSObject<FlutterMessageCodec> *PGNExampleHostApiGetCodec(void);
5353
completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;
5454
@end
5555

56-
extern void PGNExampleHostApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
56+
extern void SetUpPGNExampleHostApi(id<FlutterBinaryMessenger> binaryMessenger,
5757
NSObject<PGNExampleHostApi> *_Nullable api);
5858

5959
/// The codec used by PGNMessageFlutterApi.

packages/pigeon/example/app/macos/Runner/messages.g.m

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data {
127127
return sSharedObject;
128128
}
129129

130-
void PGNExampleHostApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
130+
void SetUpPGNExampleHostApi(id<FlutterBinaryMessenger> binaryMessenger,
131131
NSObject<PGNExampleHostApi> *api) {
132132
{
133133
FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
@@ -220,9 +220,22 @@ - (void)flutterMethodAString:(nullable NSString *)arg_aString
220220
binaryMessenger:self.binaryMessenger
221221
codec:PGNMessageFlutterApiGetCodec()];
222222
[channel sendMessage:@[ arg_aString ?: [NSNull null] ]
223-
reply:^(id reply) {
224-
NSString *output = reply;
225-
completion(output, nil);
223+
reply:^(NSArray<id> *reply) {
224+
if (reply != nil) {
225+
if (reply.count > 1) {
226+
completion(nil, [FlutterError errorWithCode:reply[0]
227+
message:reply[1]
228+
details:reply[2]]);
229+
} else {
230+
NSString *output = reply[0] == [NSNull null] ? nil : reply[0];
231+
completion(output, nil);
232+
}
233+
} else {
234+
completion(nil, [FlutterError
235+
errorWithCode:@"channel-error"
236+
message:@"Unable to establish connection on channel."
237+
details:@""]);
238+
}
226239
}];
227240
}
228241
@end

packages/pigeon/example/app/windows/runner/messages.g.cpp

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,17 +271,31 @@ void MessageFlutterApi::FlutterMethod(
271271
EncodableValue encoded_api_arguments = EncodableValue(EncodableList{
272272
a_string_arg ? EncodableValue(*a_string_arg) : EncodableValue(),
273273
});
274-
channel->Send(
275-
encoded_api_arguments,
276-
[on_success = std::move(on_success), on_error = std::move(on_error)](
277-
const uint8_t* reply, size_t reply_size) {
278-
std::unique_ptr<EncodableValue> response =
279-
GetCodec().DecodeMessage(reply, reply_size);
280-
const auto& encodable_return_value = *response;
274+
channel->Send(encoded_api_arguments, [on_success = std::move(on_success),
275+
on_error = std::move(on_error)](
276+
const uint8_t* reply,
277+
size_t reply_size) {
278+
std::unique_ptr<EncodableValue> response =
279+
GetCodec().DecodeMessage(reply, reply_size);
280+
const auto& encodable_return_value = *response;
281+
const auto* list_return_value =
282+
std::get_if<EncodableList>(&encodable_return_value);
283+
if (list_return_value) {
284+
if (list_return_value->size() > 1) {
285+
on_error(FlutterError(std::get<std::string>(list_return_value->at(0)),
286+
std::get<std::string>(list_return_value->at(1)),
287+
list_return_value->at(2)));
288+
} else {
281289
const auto& return_value =
282-
std::get<std::string>(encodable_return_value);
290+
std::get<std::string>(list_return_value->at(0));
283291
on_success(return_value);
284-
});
292+
}
293+
} else {
294+
on_error(FlutterError("channel-error",
295+
"Unable to establish connection on channel.",
296+
EncodableValue("")));
297+
}
298+
});
285299
}
286300

287301
} // namespace pigeon_example

0 commit comments

Comments
 (0)