[pigeon] implemented async handlers for objc (#282)

This commit is contained in:
gaaclarke
2021-02-16 17:13:49 -08:00
committed by GitHub
parent d377e6b741
commit 85dd70583a
12 changed files with 622 additions and 91 deletions

View File

@ -1,3 +1,8 @@
## 0.1.20
* Implemented `@async` HostApi's for iOS.
* Fixed async FlutterApi methods with void return.
## 0.1.19
* Fixed a bug introduced in 0.1.17 where methods without arguments were

View File

@ -17,7 +17,8 @@ class Node {}
/// Represents a method on an [Api].
class Method extends Node {
/// Parametric constructor for [Method].
Method({this.name, this.returnType, this.argType, this.isAsynchronous});
Method(
{this.name, this.returnType, this.argType, this.isAsynchronous = false});
/// The name of the method.
String name;

View File

@ -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 = '0.1.19';
const String pigeonVersion = '0.1.20';
/// Read all the content from [stdin] to a String.
String readStdin() {
@ -72,9 +72,7 @@ class Indent {
if (begin != null) {
_sink.write(begin + newline);
}
inc();
func();
dec();
nest(1, func);
if (end != null) {
_sink.write(str() + end);
if (addTrailingNewline) {
@ -83,6 +81,17 @@ class Indent {
}
}
/// Like `scoped` but writes the current indentation level.
void writeScoped(
String begin,
String end,
Function func, {
bool addTrailingNewline = true,
}) {
scoped(str() + begin ?? '', end, func,
addTrailingNewline: addTrailingNewline);
}
/// Scoped increase of the ident level. For the execution of [func] the
/// indentation will be incremented by the given amount.
void nest(int count, Function func) {

View File

@ -71,6 +71,91 @@ String _propertyTypeForDartType(String type) {
}
}
void _writeClassDeclarations(
Indent indent, List<Class> classes, String prefix) {
for (Class klass in classes) {
indent.writeln('@interface ${_className(prefix, klass.name)} : NSObject');
for (Field field in klass.fields) {
final HostDatatype hostDatatype = getHostDatatype(
field, classes, _objcTypeForDartType,
customResolver: (String x) => '${_className(prefix, x)} *');
final String propertyType = hostDatatype.isBuiltin
? _propertyTypeForDartType(field.dataType)
: 'strong';
final String nullability =
hostDatatype.datatype.contains('*') ? ', nullable' : '';
indent.writeln(
'@property(nonatomic, $propertyType$nullability) ${hostDatatype.datatype} ${field.name};');
}
indent.writeln('@end');
indent.writeln('');
}
}
void _writeHostApiDeclaration(Indent indent, Api api, ObjcOptions options) {
final String apiName = _className(options.prefix, api.name);
indent.writeln('@protocol $apiName');
for (Method func in api.methods) {
final String returnTypeName = _className(options.prefix, func.returnType);
if (func.isAsynchronous) {
if (func.returnType == 'void') {
if (func.argType == 'void') {
indent.writeln(
'-(void)${func.name}:(void(^)(FlutterError *_Nullable))completion;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'-(void)${func.name}:(nullable $argType *)input completion:(void(^)(FlutterError *_Nullable))completion;');
}
} else {
if (func.argType == 'void') {
indent.writeln(
'-(void)${func.name}:(void(^)($returnTypeName *_Nullable, FlutterError *_Nullable))completion;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'-(void)${func.name}:(nullable $argType *)input completion:(void(^)($returnTypeName *_Nullable, FlutterError *_Nullable))completion;');
}
}
} else {
final String returnType =
func.returnType == 'void' ? 'void' : 'nullable $returnTypeName *';
if (func.argType == 'void') {
indent.writeln(
'-($returnType)${func.name}:(FlutterError *_Nullable *_Nonnull)error;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'-($returnType)${func.name}:($argType*)input error:(FlutterError *_Nullable *_Nonnull)error;');
}
}
}
indent.writeln('@end');
indent.writeln('');
indent.writeln(
'extern void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, id<$apiName> _Nullable api);');
indent.writeln('');
}
void _writeFlutterApiDeclaration(Indent indent, Api api, ObjcOptions options) {
final String apiName = _className(options.prefix, api.name);
indent.writeln('@interface $apiName : NSObject');
indent.writeln(
'- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;');
for (Method func in api.methods) {
final String returnType = _className(options.prefix, func.returnType);
final String callbackType = _callbackForType(func.returnType, returnType);
if (func.argType == 'void') {
indent.writeln('- (void)${func.name}:($callbackType)completion;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'- (void)${func.name}:($argType*)input completion:($callbackType)completion;');
}
}
indent.writeln('@end');
}
/// Generates the ".h" file for the AST represented by [root] to [sink] with the
/// provided [options].
void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
@ -92,65 +177,13 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
indent.writeln('');
for (Class klass in root.classes) {
indent.writeln(
'@interface ${_className(options.prefix, klass.name)} : NSObject');
for (Field field in klass.fields) {
final HostDatatype hostDatatype = getHostDatatype(
field, root.classes, _objcTypeForDartType,
customResolver: (String x) => '${_className(options.prefix, x)} *');
final String propertyType = hostDatatype.isBuiltin
? _propertyTypeForDartType(field.dataType)
: 'strong';
final String nullability =
hostDatatype.datatype.contains('*') ? ', nullable' : '';
indent.writeln(
'@property(nonatomic, $propertyType$nullability) ${hostDatatype.datatype} ${field.name};');
}
indent.writeln('@end');
indent.writeln('');
}
_writeClassDeclarations(indent, root.classes, options.prefix);
for (Api api in root.apis) {
final String apiName = _className(options.prefix, api.name);
if (api.location == ApiLocation.host) {
indent.writeln('@protocol $apiName');
for (Method func in api.methods) {
final String returnTypeName =
_className(options.prefix, func.returnType);
final String returnType =
func.returnType == 'void' ? 'void' : 'nullable $returnTypeName *';
if (func.argType == 'void') {
indent.writeln(
'-($returnType)${func.name}:(FlutterError *_Nullable *_Nonnull)error;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'-($returnType)${func.name}:($argType*)input error:(FlutterError *_Nullable *_Nonnull)error;');
}
}
indent.writeln('@end');
indent.writeln('');
indent.writeln(
'extern void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, id<$apiName> _Nullable api);');
indent.writeln('');
_writeHostApiDeclaration(indent, api, options);
} else if (api.location == ApiLocation.flutter) {
indent.writeln('@interface $apiName : NSObject');
indent.writeln(
'- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;');
for (Method func in api.methods) {
final String returnType = _className(options.prefix, func.returnType);
final String callbackType =
_callbackForType(func.returnType, returnType);
if (func.argType == 'void') {
indent.writeln('- (void)${func.name}:($callbackType)completion;');
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln(
'- (void)${func.name}:($argType*)input completion:($callbackType)completion;');
}
}
indent.writeln('@end');
_writeFlutterApiDeclaration(indent, api, options);
}
}
@ -204,21 +237,56 @@ void _writeHostApiSource(Indent indent, ObjcOptions options, Api api) {
indent.scoped('{', '}];', () {
final String returnType =
_className(options.prefix, func.returnType);
indent.writeln('FlutterError *error;');
String call;
String syncCall;
if (func.argType == 'void') {
call = '[api ${func.name}:&error]';
syncCall = '[api ${func.name}:&error]';
} else {
final String argType = _className(options.prefix, func.argType);
indent.writeln('$argType *input = [$argType fromMap:message];');
call = '[api ${func.name}:input error:&error]';
syncCall = '[api ${func.name}:input error:&error]';
}
if (func.returnType == 'void') {
indent.writeln('$call;');
indent.writeln('callback(wrapResult(nil, error));');
if (func.isAsynchronous) {
if (func.returnType == 'void') {
const String callback = 'callback(error));';
if (func.argType == 'void') {
indent.writeScoped(
'[api ${func.name}:^(FlutterError *_Nullable error) {',
'}];', () {
indent.writeln(callback);
});
} else {
indent.writeScoped(
'[api ${func.name}:input completion:^(FlutterError *_Nullable error) {',
'}];', () {
indent.writeln(callback);
});
}
} else {
const String callback =
'callback(wrapResult([output toMap], error));';
if (func.argType == 'void') {
indent.writeScoped(
'[api ${func.name}:^($returnType *_Nullable output, FlutterError *_Nullable error) {',
'}];', () {
indent.writeln(callback);
});
} else {
indent.writeScoped(
'[api ${func.name}:input completion:^($returnType *_Nullable output, FlutterError *_Nullable error) {',
'}];', () {
indent.writeln(callback);
});
}
}
} else {
indent.writeln('$returnType *output = $call;');
indent.writeln('callback(wrapResult([output toMap], error));');
indent.writeln('FlutterError *error;');
if (func.returnType == 'void') {
indent.writeln('$syncCall;');
indent.writeln('callback(wrapResult(nil, error));');
} else {
indent.writeln('$returnType *output = $syncCall;');
indent.writeln('callback(wrapResult([output toMap], error));');
}
}
});
});
@ -246,7 +314,7 @@ void _writeFlutterApiSource(Indent indent, ObjcOptions options, Api api) {
indent.writeln('self = [super init];');
indent.write('if (self) ');
indent.scoped('{', '}', () {
indent.writeln('self.binaryMessenger = binaryMessenger;');
indent.writeln('_binaryMessenger = binaryMessenger;');
});
indent.writeln('return self;');
});
@ -312,19 +380,19 @@ void generateObjcSource(ObjcOptions options, Root root, StringSink sink) {
indent.addln('');
indent.format(
'''static NSDictionary* wrapResult(NSDictionary *result, FlutterError *error) {
'''static NSDictionary<NSString*, id>* wrapResult(NSDictionary *result, FlutterError *error) {
\tNSDictionary *errorDict = (NSDictionary *)[NSNull null];
\tif (error) {
\t\terrorDict = [NSDictionary dictionaryWithObjectsAndKeys:
\t\t\t\t(error.code ? error.code : [NSNull null]), @"${Keys.errorCode}",
\t\t\t\t(error.message ? error.message : [NSNull null]), @"${Keys.errorMessage}",
\t\t\t\t(error.details ? error.details : [NSNull null]), @"${Keys.errorDetails}",
\t\t\t\tnil];
\t\terrorDict = @{
\t\t\t\t@"${Keys.errorCode}": (error.code ? error.code : [NSNull null]),
\t\t\t\t@"${Keys.errorMessage}": (error.message ? error.message : [NSNull null]),
\t\t\t\t@"${Keys.errorDetails}": (error.details ? error.details : [NSNull null]),
\t\t\t\t};
\t}
\treturn [NSDictionary dictionaryWithObjectsAndKeys:
\t\t\t(result ? result : [NSNull null]), @"${Keys.result}",
\t\t\terrorDict, @"${Keys.error}",
\t\t\tnil];
\treturn @{
\t\t\t@"${Keys.result}": (result ? result : [NSNull null]),
\t\t\t@"${Keys.error}": errorDict,
\t\t\t};
}''');
indent.addln('');

View File

@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
0D50126D23FF759100CD5B95 /* messages.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D50126B23FF759100CD5B95 /* messages.m */; };
0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D50127423FF75B100CD5B95 /* RunnerTests.m */; };
0D8C35E825D45A3000B76435 /* async_handlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D8C35E725D45A3000B76435 /* async_handlers.m */; };
0D8C35EB25D45A7900B76435 /* AsyncHandlersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D8C35EA25D45A7900B76435 /* AsyncHandlersTest.m */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
@ -47,6 +49,9 @@
0D50127223FF75B100CD5B95 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0D50127423FF75B100CD5B95 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = "<group>"; };
0D50127623FF75B100CD5B95 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0D8C35E625D45A3000B76435 /* async_handlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = async_handlers.h; sourceTree = "<group>"; };
0D8C35E725D45A3000B76435 /* async_handlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = async_handlers.m; sourceTree = "<group>"; };
0D8C35EA25D45A7900B76435 /* AsyncHandlersTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AsyncHandlersTest.m; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
@ -86,6 +91,7 @@
children = (
0D50127423FF75B100CD5B95 /* RunnerTests.m */,
0D50127623FF75B100CD5B95 /* Info.plist */,
0D8C35EA25D45A7900B76435 /* AsyncHandlersTest.m */,
);
path = RunnerTests;
sourceTree = "<group>";
@ -123,6 +129,8 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
0D8C35E625D45A3000B76435 /* async_handlers.h */,
0D8C35E725D45A3000B76435 /* async_handlers.m */,
0D50126C23FF759100CD5B95 /* messages.h */,
0D50126B23FF759100CD5B95 /* messages.m */,
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
@ -283,6 +291,7 @@
buildActionMask = 2147483647;
files = (
0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */,
0D8C35EB25D45A7900B76435 /* AsyncHandlersTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -294,6 +303,7 @@
0D50126D23FF759100CD5B95 /* messages.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
0D8C35E825D45A3000B76435 /* async_handlers.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,27 @@
// Autogenerated from Pigeon (v0.1.20), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#import <Foundation/Foundation.h>
@protocol FlutterBinaryMessenger;
@class FlutterError;
@class FlutterStandardTypedData;
NS_ASSUME_NONNULL_BEGIN
@class Value;
@interface Value : NSObject
@property(nonatomic, strong, nullable) NSNumber *number;
@end
@interface Api2Flutter : NSObject
- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;
- (void)calculate:(Value *)input completion:(void (^)(Value *, NSError *_Nullable))completion;
@end
@protocol Api2Host
- (void)calculate:(nullable Value *)input
completion:(void (^)(Value *_Nullable, FlutterError *_Nullable))completion;
@end
extern void Api2HostSetup(id<FlutterBinaryMessenger> binaryMessenger, id<Api2Host> _Nullable api);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,88 @@
// Autogenerated from Pigeon (v0.1.20), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#import "async_handlers.h"
#import <Flutter/Flutter.h>
#if !__has_feature(objc_arc)
#error File requires ARC to be enabled.
#endif
static NSDictionary<NSString *, id> *wrapResult(NSDictionary *result, FlutterError *error) {
NSDictionary *errorDict = (NSDictionary *)[NSNull null];
if (error) {
errorDict = @{
@"code" : (error.code ? error.code : [NSNull null]),
@"message" : (error.message ? error.message : [NSNull null]),
@"details" : (error.details ? error.details : [NSNull null]),
};
}
return @{
@"result" : (result ? result : [NSNull null]),
@"error" : errorDict,
};
}
@interface Value ()
+ (Value *)fromMap:(NSDictionary *)dict;
- (NSDictionary *)toMap;
@end
@implementation Value
+ (Value *)fromMap:(NSDictionary *)dict {
Value *result = [[Value alloc] init];
result.number = dict[@"number"];
if ((NSNull *)result.number == [NSNull null]) {
result.number = nil;
}
return result;
}
- (NSDictionary *)toMap {
return [NSDictionary
dictionaryWithObjectsAndKeys:(self.number ? self.number : [NSNull null]), @"number", nil];
}
@end
@interface Api2Flutter ()
@property(nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;
@end
@implementation Api2Flutter
- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger {
self = [super init];
if (self) {
_binaryMessenger = binaryMessenger;
}
return self;
}
- (void)calculate:(Value *)input completion:(void (^)(Value *, NSError *_Nullable))completion {
FlutterBasicMessageChannel *channel =
[FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.Api2Flutter.calculate"
binaryMessenger:self.binaryMessenger];
NSDictionary *inputMap = [input toMap];
[channel sendMessage:inputMap
reply:^(id reply) {
NSDictionary *outputMap = reply;
Value *output = [Value fromMap:outputMap];
completion(output, nil);
}];
}
@end
void Api2HostSetup(id<FlutterBinaryMessenger> binaryMessenger, id<Api2Host> api) {
{
FlutterBasicMessageChannel *channel =
[FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.Api2Host.calculate"
binaryMessenger:binaryMessenger];
if (api) {
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
Value *input = [Value fromMap:message];
[api calculate:input
completion:^(Value *_Nullable output, FlutterError *_Nullable error) {
callback(wrapResult([output toMap], error));
}];
}];
} else {
[channel setMessageHandler:nil];
}
}
}

View File

@ -0,0 +1,139 @@
#import <Flutter/Flutter.h>
#import <XCTest/XCTest.h>
#import "async_handlers.h"
///////////////////////////////////////////////////////////////////////////////////////////
@interface Value ()
+ (Value*)fromMap:(NSDictionary*)dict;
- (NSDictionary*)toMap;
@end
///////////////////////////////////////////////////////////////////////////////////////////
@interface MockBinaryMessenger : NSObject<FlutterBinaryMessenger>
@property(nonatomic, copy) NSNumber* result;
@property(nonatomic, retain) FlutterStandardMessageCodec* codec;
@property(nonatomic, retain) NSMutableDictionary<NSString*, FlutterBinaryMessageHandler>* handlers;
@end
///////////////////////////////////////////////////////////////////////////////////////////
@implementation MockBinaryMessenger
- (instancetype)init {
self = [super init];
if (self) {
_codec = [FlutterStandardMessageCodec sharedInstance];
_handlers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)cleanupConnection:(FlutterBinaryMessengerConnection)connection {
}
- (void)sendOnChannel:(nonnull NSString*)channel message:(NSData* _Nullable)message {
}
- (void)sendOnChannel:(nonnull NSString*)channel
message:(NSData* _Nullable)message
binaryReply:(FlutterBinaryReply _Nullable)callback {
if (self.result) {
Value* output = [[Value alloc] init];
output.number = self.result;
NSDictionary* outputDictionary = [output toMap];
callback([_codec encode:outputDictionary]);
}
}
- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString*)channel
binaryMessageHandler:
(FlutterBinaryMessageHandler _Nullable)handler {
_handlers[channel] = [handler copy];
return _handlers.count;
}
@end
///////////////////////////////////////////////////////////////////////////////////////////
@interface MockApi2Host : NSObject<Api2Host>
@property(nonatomic, copy) NSNumber* output;
@end
///////////////////////////////////////////////////////////////////////////////////////////
@implementation MockApi2Host
- (void)calculate:(Value* _Nullable)input
completion:(nonnull void (^)(Value* _Nullable, FlutterError* _Nullable))completion {
if (self.output) {
Value* output = [[Value alloc] init];
output.number = self.output;
completion(output, nil);
} else {
completion(nil, [FlutterError errorWithCode:@"hey" message:@"ho" details:nil]);
}
}
@end
///////////////////////////////////////////////////////////////////////////////////////////
@interface AsyncHandlersTest : XCTestCase
@end
///////////////////////////////////////////////////////////////////////////////////////////
@implementation AsyncHandlersTest
- (void)testAsyncHost2Flutter {
MockBinaryMessenger* binaryMessenger = [[MockBinaryMessenger alloc] init];
binaryMessenger.result = @(2);
Api2Flutter* api2Flutter = [[Api2Flutter alloc] initWithBinaryMessenger:binaryMessenger];
Value* input = [[Value alloc] init];
input.number = @(1);
XCTestExpectation* expectation = [self expectationWithDescription:@"calculate callback"];
[api2Flutter calculate:input
completion:^(Value* _Nonnull output, NSError* _Nullable error) {
XCTAssertEqual(output.number.intValue, 2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:1.0 handler:nil];
}
- (void)testAsyncFlutter2Host {
MockBinaryMessenger* binaryMessenger = [[MockBinaryMessenger alloc] init];
MockApi2Host* mockApi2Host = [[MockApi2Host alloc] init];
mockApi2Host.output = @(2);
Api2HostSetup(binaryMessenger, mockApi2Host);
NSString* channelName = @"dev.flutter.pigeon.Api2Host.calculate";
XCTAssertNotNil(binaryMessenger.handlers[channelName]);
Value* input = [[Value alloc] init];
input.number = @(1);
NSData* inputEncoded = [binaryMessenger.codec encode:[input toMap]];
XCTestExpectation* expectation = [self expectationWithDescription:@"calculate callback"];
binaryMessenger.handlers[channelName](inputEncoded, ^(NSData* data) {
NSDictionary* outputMap = [binaryMessenger.codec decode:data];
Value* output = [Value fromMap:outputMap[@"result"]];
XCTAssertEqual(output.number.intValue, 2);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:1.0 handler:nil];
}
- (void)testAsyncFlutter2HostError {
MockBinaryMessenger* binaryMessenger = [[MockBinaryMessenger alloc] init];
MockApi2Host* mockApi2Host = [[MockApi2Host alloc] init];
Api2HostSetup(binaryMessenger, mockApi2Host);
NSString* channelName = @"dev.flutter.pigeon.Api2Host.calculate";
XCTAssertNotNil(binaryMessenger.handlers[channelName]);
Value* input = [[Value alloc] init];
input.number = @(1);
NSData* inputEncoded = [binaryMessenger.codec encode:[input toMap]];
XCTestExpectation* expectation = [self expectationWithDescription:@"calculate callback"];
binaryMessenger.handlers[channelName](inputEncoded, ^(NSData* data) {
NSDictionary* outputMap = [binaryMessenger.codec decode:data];
XCTAssertNotNil(outputMap[@"error"]);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:1.0 handler:nil];
}
@end

View File

@ -1,11 +1,3 @@
//
// RunnerTests.m
// RunnerTests
//
// Created by Aaron Clarke on 2/20/20.
// Copyright © 2020 The Chromium Authors. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "messages.h"

View File

@ -1,5 +1,5 @@
name: pigeon
version: 0.1.19 # This must match the version in lib/generator_tools.dart
version: 0.1.20 # This must match the version in lib/generator_tools.dart
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
homepage: https://github.com/flutter/packages/tree/master/packages/pigeon
dependencies:

View File

@ -134,6 +134,7 @@ pushd $PWD
cd e2e_tests/test_objc/
flutter pub get
popd
test_pigeon_ios ./pigeons/async_handlers.dart
test_null_safe_dart ./pigeons/message.dart
test_pigeon_android ./pigeons/voidflutter.dart
test_pigeon_android ./pigeons/voidhost.dart
@ -152,8 +153,6 @@ test_pigeon_ios ./pigeons/void_arg_host.dart
test_pigeon_ios ./pigeons/void_arg_flutter.dart
test_pigeon_ios ./pigeons/list.dart
test_pigeon_ios ./pigeons/all_datatypes.dart
# Not implemented yet.
# test_pigeon_ios ./pigeons/async_handlers.dart
###############################################################################
# iOS unit tests on generated code.
@ -163,8 +162,15 @@ pub run pigeon \
--dart_out /dev/null \
--objc_header_out platform_tests/ios_unit_tests/ios/Runner/messages.h \
--objc_source_out platform_tests/ios_unit_tests/ios/Runner/messages.m
pub run pigeon \
--input pigeons/async_handlers.dart \
--dart_out /dev/null \
--objc_header_out platform_tests/ios_unit_tests/ios/Runner/async_handlers.h \
--objc_source_out platform_tests/ios_unit_tests/ios/Runner/async_handlers.m
clang-format -i platform_tests/ios_unit_tests/ios/Runner/messages.h
clang-format -i platform_tests/ios_unit_tests/ios/Runner/messages.m
clang-format -i platform_tests/ios_unit_tests/ios/Runner/async_handlers.h
clang-format -i platform_tests/ios_unit_tests/ios/Runner/async_handlers.m
pushd $PWD
cd platform_tests/ios_unit_tests
flutter build ios --simulator

View File

@ -431,4 +431,190 @@ void main() {
expect(code, contains('@interface Foobar'));
expect(code, matches('@property.*NSDictionary.*field1'));
});
test('async void(input) HostApi header', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'Input',
returnType: 'void',
isAsynchronous: true)
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[Field(name: 'input', dataType: 'String')]),
Class(
name: 'Output',
fields: <Field>[Field(name: 'output', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateObjcHeader(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'(void)doSomething:(nullable ABCInput *)input completion:(void(^)(FlutterError *_Nullable))completion'));
});
test('async output(input) HostApi header', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'Input',
returnType: 'Output',
isAsynchronous: true)
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[Field(name: 'input', dataType: 'String')]),
Class(
name: 'Output',
fields: <Field>[Field(name: 'output', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateObjcHeader(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'(void)doSomething:(nullable ABCInput *)input completion:(void(^)(ABCOutput *_Nullable, FlutterError *_Nullable))completion'));
});
test('async output(void) HostApi header', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'void',
returnType: 'Output',
isAsynchronous: true)
])
], classes: <Class>[
Class(
name: 'Output',
fields: <Field>[Field(name: 'output', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateObjcHeader(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'(void)doSomething:(void(^)(ABCOutput *_Nullable, FlutterError *_Nullable))completion'));
});
test('async void(void) HostApi header', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'void',
returnType: 'void',
isAsynchronous: true)
])
], classes: <Class>[]);
final StringBuffer sink = StringBuffer();
generateObjcHeader(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'(void)doSomething:(void(^)(FlutterError *_Nullable))completion'));
});
test('async output(input) HostApi source', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'Input',
returnType: 'Output',
isAsynchronous: true)
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[Field(name: 'input', dataType: 'String')]),
Class(
name: 'Output',
fields: <Field>[Field(name: 'output', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateObjcSource(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'[api doSomething:input completion:^(ABCOutput *_Nullable output, FlutterError *_Nullable error) {'));
});
test('async void(input) HostApi source', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'Input',
returnType: 'void',
isAsynchronous: true)
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[Field(name: 'input', dataType: 'String')]),
Class(
name: 'Output',
fields: <Field>[Field(name: 'output', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateObjcSource(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'[api doSomething:input completion:^(FlutterError *_Nullable error) {'));
});
test('async void(void) HostApi source', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'void',
returnType: 'void',
isAsynchronous: true)
])
], classes: <Class>[]);
final StringBuffer sink = StringBuffer();
generateObjcSource(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code, contains('[api doSomething:^(FlutterError *_Nullable error) {'));
});
test('async output(void) HostApi source', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(
name: 'doSomething',
argType: 'void',
returnType: 'Output',
isAsynchronous: true)
])
], classes: <Class>[
Class(
name: 'Output',
fields: <Field>[Field(name: 'output', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateObjcSource(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(
code,
contains(
'[api doSomething:^(ABCOutput *_Nullable output, FlutterError *_Nullable error) {'));
});
}