Skip to content

Commit 1d788b4

Browse files
[pigeon] Async error handling for kotlin and swift (#3102)
* temp * sets up use of wrapError * remove swift from skip list, update wrapError func * changelog * macos tests * Stacktrace label * nits * async wont try, do, throw, or catch * Async error handling * throwAsyncError all but kotlin + c++ * conflicts * typo * } * revert bad merge * more try * Revert "more try" This reverts commit 2d80efe. * kotlin * kotlin wrapError, cpp attempt * Fix Windows implementation * adds async error from void method integration test * changelog * windows * fix null returns on async kotlin * nits * nits * typo --------- Co-authored-by: Stuart Morgan <stuartmorgan@google.com>
1 parent 9f8c807 commit 1d788b4

36 files changed

Lines changed: 1380 additions & 525 deletions

File tree

packages/pigeon/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 7.2.0
2+
3+
* [swift] Changes async method completion types.
4+
May require code updates to existing code.
5+
* [swift] Adds error handling to async methods.
6+
* [kotlin] Changes async method completion types.
7+
May require code updates to existing code.
8+
* [kotlin] Adds error handling to async methods.
9+
* Adds async error handling integration tests for all platforms.
10+
111
## 7.1.5
212

313
* Updates code to fix strict-cast violations.

packages/pigeon/lib/generator_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import 'ast.dart';
1111
/// The current version of pigeon.
1212
///
1313
/// This must match the version in pubspec.yaml.
14-
const String pigeonVersion = '7.1.5';
14+
const String pigeonVersion = '7.2.0';
1515

1616
/// Read all the content from [stdin] to a String.
1717
String readStdin() {

packages/pigeon/lib/kotlin_generator.dart

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -440,11 +440,13 @@ class KotlinGenerator extends StructuredGenerator<KotlinOptions> {
440440
? ''
441441
: _nullsafeKotlinTypeForDartType(method.returnType);
442442

443+
final String resultType =
444+
method.returnType.isVoid ? 'Unit' : returnType;
443445
addDocumentationComments(
444446
indent, method.documentationComments, _docCommentSpec);
445447

446448
if (method.isAsynchronous) {
447-
argSignature.add('callback: ($returnType) -> Unit');
449+
argSignature.add('callback: (Result<$resultType>) -> Unit');
448450
indent.writeln('fun ${method.name}(${argSignature.join(', ')})');
449451
} else if (method.returnType.isVoid) {
450452
indent.writeln('fun ${method.name}(${argSignature.join(', ')})');
@@ -501,43 +503,54 @@ class KotlinGenerator extends StructuredGenerator<KotlinOptions> {
501503
indent.write('channel.setMessageHandler ');
502504
indent.addScoped('{ $messageVarName, reply ->', '}', () {
503505
indent.writeln('var wrapped = listOf<Any?>()');
504-
indent.write('try ');
505-
indent.addScoped('{', '}', () {
506-
final List<String> methodArgument = <String>[];
507-
if (method.arguments.isNotEmpty) {
508-
indent.writeln('val args = message as List<Any?>');
509-
enumerate(method.arguments, (int index, NamedType arg) {
510-
final String argName = _getSafeArgumentName(index, arg);
511-
final String argIndex = 'args[$index]';
512-
indent.writeln(
513-
'val $argName = ${_castForceUnwrap(argIndex, arg.type, root)}');
514-
methodArgument.add(argName);
515-
});
516-
}
517-
final String call =
518-
'api.${method.name}(${methodArgument.join(', ')})';
519-
if (method.isAsynchronous) {
520-
indent.write('$call ');
521-
final String resultValue =
522-
method.returnType.isVoid ? 'null' : 'it';
523-
indent.addScoped('{', '}', () {
524-
indent.writeln('reply.reply(wrapResult($resultValue))');
506+
final List<String> methodArguments = <String>[];
507+
if (method.arguments.isNotEmpty) {
508+
indent.writeln('val args = message as List<Any?>');
509+
enumerate(method.arguments, (int index, NamedType arg) {
510+
final String argName = _getSafeArgumentName(index, arg);
511+
final String argIndex = 'args[$index]';
512+
indent.writeln(
513+
'val $argName = ${_castForceUnwrap(argIndex, arg.type, root)}');
514+
methodArguments.add(argName);
515+
});
516+
}
517+
final String call =
518+
'api.${method.name}(${methodArguments.join(', ')})';
519+
520+
if (method.isAsynchronous) {
521+
indent.write('$call ');
522+
final String resultType = method.returnType.isVoid
523+
? 'Unit'
524+
: _nullsafeKotlinTypeForDartType(method.returnType);
525+
indent.addScoped('{ result: Result<$resultType> ->', '}',
526+
() {
527+
indent.writeln('val error = result.exceptionOrNull()');
528+
indent.writeScoped('if (error != null) {', '}', () {
529+
indent.writeln('reply.reply(wrapError(error))');
530+
}, addTrailingNewline: false);
531+
indent.addScoped(' else {', '}', () {
532+
if (method.returnType.isVoid) {
533+
indent.writeln('reply.reply(wrapResult(null))');
534+
} else {
535+
indent.writeln('val data = result.getOrNull()');
536+
indent.writeln('reply.reply(wrapResult(data))');
537+
}
525538
});
526-
} else if (method.returnType.isVoid) {
527-
indent.writeln(call);
528-
indent.writeln('wrapped = listOf<Any?>(null)');
529-
} else {
530-
indent.writeln('wrapped = listOf<Any?>($call)');
531-
}
532-
}, addTrailingNewline: false);
533-
indent.add(' catch (exception: Error) ');
534-
indent.addScoped('{', '}', () {
535-
indent.writeln('wrapped = wrapError(exception)');
536-
if (method.isAsynchronous) {
537-
indent.writeln('reply.reply(wrapped)');
538-
}
539-
});
540-
if (!method.isAsynchronous) {
539+
});
540+
} else {
541+
indent.write('try ');
542+
indent.addScoped('{', '}', () {
543+
if (method.returnType.isVoid) {
544+
indent.writeln(call);
545+
indent.writeln('wrapped = listOf<Any?>(null)');
546+
} else {
547+
indent.writeln('wrapped = listOf<Any?>($call)');
548+
}
549+
}, addTrailingNewline: false);
550+
indent.add(' catch (exception: Error) ');
551+
indent.addScoped('{', '}', () {
552+
indent.writeln('wrapped = wrapError(exception)');
553+
});
541554
indent.writeln('reply.reply(wrapped)');
542555
}
543556
});

packages/pigeon/lib/swift_generator.dart

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -395,13 +395,18 @@ import FlutterMacOS
395395
}).toList();
396396

397397
final String returnType = method.returnType.isVoid
398-
? ''
398+
? 'Void'
399399
: _nullsafeSwiftTypeForDartType(method.returnType);
400+
401+
final String escapeType =
402+
method.returnType.isVoid ? 'Void' : returnType;
403+
400404
addDocumentationComments(
401405
indent, method.documentationComments, _docCommentSpec);
402406

403407
if (method.isAsynchronous) {
404-
argSignature.add('completion: @escaping ($returnType) -> Void');
408+
argSignature.add(
409+
'completion: @escaping (Result<$escapeType, Error>) -> Void');
405410
indent.writeln('func ${components.name}(${argSignature.join(', ')})');
406411
} else if (method.returnType.isVoid) {
407412
indent.writeln(
@@ -470,16 +475,25 @@ import FlutterMacOS
470475
final String call =
471476
'${tryStatement}api.${components.name}(${methodArgument.join(', ')})';
472477
if (method.isAsynchronous) {
478+
final String resultName =
479+
method.returnType.isVoid ? 'nil' : 'res';
480+
final String successVariableInit =
481+
method.returnType.isVoid ? '' : '(let res)';
473482
indent.write('$call ');
474-
if (method.returnType.isVoid) {
483+
484+
indent.addScoped('{ result in', '}', () {
485+
indent.write('switch result ');
475486
indent.addScoped('{', '}', () {
476-
indent.writeln('reply(wrapResult(nil))');
477-
});
478-
} else {
479-
indent.addScoped('{ result in', '}', () {
480-
indent.writeln('reply(wrapResult(result))');
487+
indent.writeln('case .success$successVariableInit:');
488+
indent.nest(1, () {
489+
indent.writeln('reply(wrapResult($resultName))');
490+
});
491+
indent.writeln('case .failure(let error):');
492+
indent.nest(1, () {
493+
indent.writeln('reply(wrapError(error))');
494+
});
481495
});
482-
}
496+
});
483497
} else {
484498
indent.write('do ');
485499
indent.addScoped('{', '}', () {

packages/pigeon/mock_handler_tester/test/message.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v7.1.5), do not edit directly.
5+
// Autogenerated from Pigeon (v7.2.0), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77
// 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
88

packages/pigeon/mock_handler_tester/test/test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v7.1.5), do not edit directly.
5+
// Autogenerated from Pigeon (v7.2.0), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import
88
// ignore_for_file: avoid_relative_lib_imports

packages/pigeon/pigeons/core_tests.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ abstract class HostIntegrationCoreApi {
200200
@SwiftFunction('echoAsync(_:)')
201201
String echoAsyncString(String aString);
202202

203+
/// Responds with an error from an async function returning a value.
204+
@async
205+
Object? throwAsyncError();
206+
207+
/// Responds with an error from an async void function.
208+
@async
209+
void throwAsyncErrorFromVoid();
210+
203211
// ========== Flutter API test wrappers ==========
204212

205213
@async

packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ public void echoAsyncString(@NonNull String aString, Result<String> result) {
146146
result.success(aString);
147147
}
148148

149+
@Override
150+
public void throwAsyncError(Result<Object> result) {
151+
result.error(new RuntimeException("An error"));
152+
}
153+
154+
@Override
155+
public void throwAsyncErrorFromVoid(Result<Void> result) {
156+
result.error(new RuntimeException("An error"));
157+
}
158+
149159
@Override
150160
public void callFlutterNoop(Result<Void> result) {
151161
flutterApi.noop(

packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44
//
5-
// Autogenerated from Pigeon (v7.1.5), do not edit directly.
5+
// Autogenerated from Pigeon (v7.2.0), do not edit directly.
66
// See also: https://pub.dev/packages/pigeon
77

88
package com.example.alternate_language_test_plugin;
@@ -828,6 +828,10 @@ AllNullableTypes sendMultipleNullableTypes(
828828
void noopAsync(Result<Void> result);
829829
/** Returns the passed string asynchronously. */
830830
void echoAsyncString(@NonNull String aString, Result<String> result);
831+
/** Responds with an error from an async function returning a value. */
832+
void throwAsyncError(Result<Object> result);
833+
/** Responds with an error from an async void function. */
834+
void throwAsyncErrorFromVoid(Result<Void> result);
831835

832836
void callFlutterNoop(Result<Void> result);
833837

@@ -1464,6 +1468,74 @@ public void error(Throwable error) {
14641468
channel.setMessageHandler(null);
14651469
}
14661470
}
1471+
{
1472+
BasicMessageChannel<Object> channel =
1473+
new BasicMessageChannel<>(
1474+
binaryMessenger,
1475+
"dev.flutter.pigeon.HostIntegrationCoreApi.throwAsyncError",
1476+
getCodec());
1477+
if (api != null) {
1478+
channel.setMessageHandler(
1479+
(message, reply) -> {
1480+
ArrayList<Object> wrapped = new ArrayList<Object>();
1481+
try {
1482+
Result<Object> resultCallback =
1483+
new Result<Object>() {
1484+
public void success(Object result) {
1485+
wrapped.add(0, result);
1486+
reply.reply(wrapped);
1487+
}
1488+
1489+
public void error(Throwable error) {
1490+
ArrayList<Object> wrappedError = wrapError(error);
1491+
reply.reply(wrappedError);
1492+
}
1493+
};
1494+
1495+
api.throwAsyncError(resultCallback);
1496+
} catch (Error | RuntimeException exception) {
1497+
ArrayList<Object> wrappedError = wrapError(exception);
1498+
reply.reply(wrappedError);
1499+
}
1500+
});
1501+
} else {
1502+
channel.setMessageHandler(null);
1503+
}
1504+
}
1505+
{
1506+
BasicMessageChannel<Object> channel =
1507+
new BasicMessageChannel<>(
1508+
binaryMessenger,
1509+
"dev.flutter.pigeon.HostIntegrationCoreApi.throwAsyncErrorFromVoid",
1510+
getCodec());
1511+
if (api != null) {
1512+
channel.setMessageHandler(
1513+
(message, reply) -> {
1514+
ArrayList<Object> wrapped = new ArrayList<Object>();
1515+
try {
1516+
Result<Void> resultCallback =
1517+
new Result<Void>() {
1518+
public void success(Void result) {
1519+
wrapped.add(0, null);
1520+
reply.reply(wrapped);
1521+
}
1522+
1523+
public void error(Throwable error) {
1524+
ArrayList<Object> wrappedError = wrapError(error);
1525+
reply.reply(wrappedError);
1526+
}
1527+
};
1528+
1529+
api.throwAsyncErrorFromVoid(resultCallback);
1530+
} catch (Error | RuntimeException exception) {
1531+
ArrayList<Object> wrappedError = wrapError(exception);
1532+
reply.reply(wrappedError);
1533+
}
1534+
});
1535+
} else {
1536+
channel.setMessageHandler(null);
1537+
}
1538+
}
14671539
{
14681540
BasicMessageChannel<Object> channel =
14691541
new BasicMessageChannel<>(

packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ - (void)echoAsyncString:(NSString *)aString
132132
completion(aString, nil);
133133
}
134134

135+
- (void)throwAsyncErrorWithCompletion:(void (^)(id _Nullable, FlutterError *_Nullable))completion {
136+
completion(nil, [FlutterError errorWithCode:@"An error" message:nil details:nil]);
137+
}
138+
139+
- (void)throwAsyncErrorFromVoidWithCompletion:(void (^)(FlutterError *_Nullable))completion {
140+
completion([FlutterError errorWithCode:@"An error" message:nil details:nil]);
141+
}
142+
135143
- (void)callFlutterNoopWithCompletion:(void (^)(FlutterError *_Nullable))completion {
136144
[self.flutterAPI noopWithCompletion:^(NSError *error) {
137145
completion(error);

0 commit comments

Comments
 (0)