Skip to content

Commit

Permalink
[pigeon] made it so async java handlers can report errors (flutter#460)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaaclarke authored Sep 8, 2021
1 parent 3de1bd3 commit d8c10c7
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 13 deletions.
5 changes: 5 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.0.2

* [java] Made it so `@async` handlers in `@HostApi()` can report errors
explicitly.

## 1.0.1
* [front-end] Fixed bug where classes only referenced as type arguments for
generics weren't being generated.
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/lib/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'dart:mirrors';
import 'ast.dart';

/// The current version of pigeon. This must match the version in pubspec.yaml.
const String pigeonVersion = '1.0.1';
const String pigeonVersion = '1.0.2';

/// Read all the content from [stdin] to a String.
String readStdin() {
Expand Down
25 changes: 18 additions & 7 deletions packages/pigeon/lib/java_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ static MessageCodec<Object> getCodec() {
indent.scoped('{', '} else {', () {
indent.write('channel.setMessageHandler((message, reply) -> ');
indent.scoped('{', '});', () {
final String returnType = _javaTypeForDartType(method.returnType);
final String returnType = method.returnType.isVoid
? 'Void'
: _javaTypeForDartType(method.returnType);
indent.writeln('Map<String, Object> wrapped = new HashMap<>();');
indent.write('try ');
indent.scoped('{', '}', () {
Expand All @@ -184,12 +186,20 @@ static MessageCodec<Object> getCodec() {
if (method.isAsynchronous) {
final String resultValue =
method.returnType.isVoid ? 'null' : 'result';
methodArgument.add(
'result -> { '
'wrapped.put("${Keys.result}", $resultValue); '
'reply.reply(wrapped); '
'}',
);
const String resultName = 'resultCallback';
indent.format('''
Result<$returnType> $resultName = new Result<$returnType>() {
\tpublic void success($returnType result) {
\t\twrapped.put("${Keys.result}", $resultValue);
\t\treply.reply(wrapped);
\t}
\tpublic void error(Throwable error) {
\t\twrapped.put("${Keys.error}", wrapError(error));
\t\treply.reply(wrapped);
\t}
};
''');
methodArgument.add(resultName);
}
final String call =
'api.${method.name}(${methodArgument.join(', ')})';
Expand Down Expand Up @@ -493,6 +503,7 @@ void generateJava(JavaOptions options, Root root, StringSink sink) {
indent.write('public interface Result<T> ');
indent.scoped('{', '}', () {
indent.writeln('void success(T result);');
indent.writeln('void error(Throwable error);');
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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.

package com.example.android_unit_tests;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import com.example.android_unit_tests.AsyncHandlers.*;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import java.nio.ByteBuffer;
import java.util.Map;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

public class AsyncTest {
class Success implements Api2Host {
@Override
public void calculate(Value value, Result<Value> result) {
result.success(value);
}

@Override
public void voidVoid(Result<Void> result) {
result.success(null);
}
}

class Error implements Api2Host {
@Override
public void calculate(Value value, Result<Value> result) {
result.error(new Exception("error"));
}

@Override
public void voidVoid(Result<Void> result) {
result.error(new Exception("error"));
}
}

@Test
public void asyncSuccess() {
Success api = new Success();
BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
Api2Host.setup(binaryMessenger, api);
ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> handler =
ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
verify(binaryMessenger).setMessageHandler(eq("dev.flutter.pigeon.Api2Host.calculate"), any());
verify(binaryMessenger)
.setMessageHandler(eq("dev.flutter.pigeon.Api2Host.voidVoid"), handler.capture());
MessageCodec<Object> codec = Pigeon.Api.getCodec();
ByteBuffer message = codec.encodeMessage(null);
Boolean[] didCall = {false};
handler
.getValue()
.onMessage(
message,
(bytes) -> {
bytes.rewind();
@SuppressWarnings("unchecked")
Map<String, Object> wrapped = (Map<String, Object>) codec.decodeMessage(bytes);
assertTrue(wrapped.containsKey("result"));
didCall[0] = true;
});
assertTrue(didCall[0]);
}

@Test
public void asyncError() {
Error api = new Error();
BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
Api2Host.setup(binaryMessenger, api);
ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> handler =
ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
verify(binaryMessenger).setMessageHandler(eq("dev.flutter.pigeon.Api2Host.calculate"), any());
verify(binaryMessenger)
.setMessageHandler(eq("dev.flutter.pigeon.Api2Host.voidVoid"), handler.capture());
MessageCodec<Object> codec = Pigeon.Api.getCodec();
ByteBuffer message = codec.encodeMessage(null);
Boolean[] didCall = {false};
handler
.getValue()
.onMessage(
message,
(bytes) -> {
bytes.rewind();
@SuppressWarnings("unchecked")
Map<String, Object> wrapped = (Map<String, Object>) codec.decodeMessage(bytes);
assertTrue(wrapped.containsKey("error"));
assertEquals(
"java.lang.Exception: error", ((Map) wrapped.get("error")).get("message"));
didCall[0] = true;
});
assertTrue(didCall[0]);
}
}
2 changes: 1 addition & 1 deletion packages/pigeon/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/master/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
version: 1.0.1 # This must match the version in lib/generator_tools.dart
version: 1.0.2 # This must match the version in lib/generator_tools.dart

environment:
sdk: '>=2.12.0 <3.0.0'
Expand Down
6 changes: 2 additions & 4 deletions packages/pigeon/test/java_generator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,10 @@ void main() {
final String code = sink.toString();
expect(code, contains('public interface Api'));
expect(code, contains('public interface Result<T> {'));
expect(code, contains('void error(Throwable error);'));
expect(
code, contains('void doSomething(Input arg, Result<Output> result);'));
expect(
code,
contains(
'api.doSomething(argArg, result -> { wrapped.put("result", result); reply.reply(wrapped); });'));
expect(code, contains('api.doSomething(argArg, resultCallback);'));
expect(code, contains('channel.setMessageHandler(null)'));
});

Expand Down

0 comments on commit d8c10c7

Please sign in to comment.