mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 06:37:17 +08:00
[pigeon] implemented async handlers for objc (#282)
This commit is contained in:
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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('');
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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"
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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) {'));
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user