diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 3ac6ce9452fd..c83d80a7db43 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.1.0
+
+* Added pigeon.dart.
+* Fixed some Obj-C linter problems.
+* Added the ability to generate a mock handler in Dart.
+
## 0.1.0-experimental.11
* Fixed setting an api to null in Java.
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index a584f45720ce..18525a1c22cc 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -44,14 +44,14 @@ doesn't need to worry about conflicting versions of Pigeon.
### Rules for defining your communication interface
-1) The file should contain no methods or function definitions.
+1) The file should contain no method or function definitions.
1) Datatypes are defined as classes with fields of the supported datatypes (see
the supported Datatypes section).
1) Api's should be defined as an `abstract class` with either `HostApi()` or
`FlutterApi()` as metadata. The former being for procedures that are defined
on the host platform and the latter for procedures that are defined in Dart.
1) Method declarations on the Api classes should have one argument and a return
- value whose types are defined in the file.
+ value whose types are defined in the file or be `void`.
## Example
diff --git a/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs b/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs
index e8895216fd3c..861b6d866bcf 100644
--- a/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs
+++ b/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs
@@ -1,2 +1,13 @@
+arguments=
+auto.sync=false
+build.scans.enabled=false
+connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
+gradle.user.home=
+java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home
+jvm.arguments=
+offline.mode=false
+override.workspace.settings=true
+show.console.view=true
+show.executions.view=true
diff --git a/packages/pigeon/e2e_tests/test_objc/android/app/.classpath b/packages/pigeon/e2e_tests/test_objc/android/app/.classpath
index 358909413920..4a04201ca283 100644
--- a/packages/pigeon/e2e_tests/test_objc/android/app/.classpath
+++ b/packages/pigeon/e2e_tests/test_objc/android/app/.classpath
@@ -1,6 +1,6 @@
-
+
diff --git a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h
index 4371da8ba016..8ef38d789b61 100644
--- a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h
+++ b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h
@@ -1,4 +1,4 @@
-// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
+// Autogenerated from Pigeon (v0.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#import
@protocol FlutterBinaryMessenger;
@@ -31,15 +31,17 @@ NS_ASSUME_NONNULL_BEGIN
- (void)search:(ACSearchRequest *)input completion:(void (^)(ACSearchReply *, NSError *))completion;
@end
@protocol ACNestedApi
-- (ACSearchReply *)search:(ACNested *)input error:(FlutterError *_Nullable *_Nonnull)error;
+- (nullable ACSearchReply *)search:(ACNested *)input error:(FlutterError *_Nullable *_Nonnull)error;
@end
-extern void ACNestedApiSetup(id binaryMessenger, id api);
+extern void ACNestedApiSetup(id binaryMessenger,
+ id _Nullable api);
@protocol ACApi
-- (ACSearchReply *)search:(ACSearchRequest *)input error:(FlutterError *_Nullable *_Nonnull)error;
+- (nullable ACSearchReply *)search:(ACSearchRequest *)input
+ error:(FlutterError *_Nullable *_Nonnull)error;
@end
-extern void ACApiSetup(id binaryMessenger, id api);
+extern void ACApiSetup(id binaryMessenger, id _Nullable api);
NS_ASSUME_NONNULL_END
diff --git a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m
index bf419cc90013..2865b26a3be7 100644
--- a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m
+++ b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m
@@ -1,4 +1,4 @@
-// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
+// Autogenerated from Pigeon (v0.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#import "dartle.h"
#import
diff --git a/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart b/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart
index f31480722717..5c76d82f0221 100644
--- a/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart
+++ b/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart
@@ -1,4 +1,4 @@
-// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly.
+// Autogenerated from Pigeon (v0.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
import 'dart:async';
@@ -66,18 +66,18 @@ class Nested {
abstract class FlutterSearchApi {
SearchReply search(SearchRequest arg);
-}
-
-void FlutterSearchApiSetup(FlutterSearchApi api) {
- {
- const BasicMessageChannel channel = BasicMessageChannel(
- 'dev.flutter.pigeon.FlutterSearchApi.search', StandardMessageCodec());
- channel.setMessageHandler((dynamic message) async {
- final Map mapMessage = message as Map;
- final SearchRequest input = SearchRequest._fromMap(mapMessage);
- final SearchReply output = api.search(input);
- return output._toMap();
- });
+ static void setup(FlutterSearchApi api) {
+ {
+ const BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FlutterSearchApi.search', StandardMessageCodec());
+ channel.setMessageHandler((dynamic message) async {
+ final Map mapMessage =
+ message as Map;
+ final SearchRequest input = SearchRequest._fromMap(mapMessage);
+ final SearchReply output = api.search(input);
+ return output._toMap();
+ });
+ }
}
}
@@ -128,3 +128,20 @@ class Api {
}
}
}
+
+abstract class TestHostApi {
+ SearchReply search(SearchRequest arg);
+ static void setup(TestHostApi api) {
+ {
+ const BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.Api.search', StandardMessageCodec());
+ channel.setMockMessageHandler((dynamic message) async {
+ final Map mapMessage =
+ message as Map;
+ final SearchRequest input = SearchRequest._fromMap(mapMessage);
+ final SearchReply output = api.search(input);
+ return {'result': output._toMap()};
+ });
+ }
+ }
+}
diff --git a/packages/pigeon/e2e_tests/test_objc/lib/main.dart b/packages/pigeon/e2e_tests/test_objc/lib/main.dart
index d0c86b0dd139..d9589edb46db 100644
--- a/packages/pigeon/e2e_tests/test_objc/lib/main.dart
+++ b/packages/pigeon/e2e_tests/test_objc/lib/main.dart
@@ -10,7 +10,7 @@ class _MyFlutterSearchApi extends FlutterSearchApi {
void main() {
WidgetsFlutterBinding.ensureInitialized();
- FlutterSearchApiSetup(_MyFlutterSearchApi());
+ FlutterSearchApi.setup(_MyFlutterSearchApi());
runApp(MyApp());
}
diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md
index 00f39ce7c84e..6c422ac43dd1 100644
--- a/packages/pigeon/example/README.md
+++ b/packages/pigeon/example/README.md
@@ -3,7 +3,7 @@
## message.dart
```dart
-import 'package:pigeon/pigeon_lib.dart';
+import 'package:pigeon/pigeon.dart';
class SearchRequest {
String query;
diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart
index 6c0e42d70ce9..f67ab739460a 100644
--- a/packages/pigeon/lib/ast.dart
+++ b/packages/pigeon/lib/ast.dart
@@ -32,7 +32,7 @@ class Method extends Node {
/// Represents a collection of [Method]s that are hosted ona given [location].
class Api extends Node {
/// Parametric constructor for [Api].
- Api({this.name, this.location, this.methods});
+ Api({this.name, this.location, this.methods, this.dartHostTestHandler});
/// The name of the API.
String name;
@@ -42,6 +42,9 @@ class Api extends Node {
/// List of methods inside the API.
List methods;
+
+ /// The name of the Dart test interface to generate to help with testing.
+ String dartHostTestHandler;
}
/// Represents a field on a [Class].
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index f0120cc0ed67..989f9d3a9258 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -60,7 +60,8 @@ if (replyMap == null) {
indent.writeln('');
}
-void _writeFlutterApi(Indent indent, Api api) {
+void _writeFlutterApi(Indent indent, Api api,
+ {String Function(Method) channelNameFunc, bool isMockHandler = false}) {
assert(api.location == ApiLocation.flutter);
indent.write('abstract class ${api.name} ');
indent.scoped('{', '}', () {
@@ -69,44 +70,57 @@ void _writeFlutterApi(Indent indent, Api api) {
func.argType == 'void' ? '' : '${func.argType} arg';
indent.writeln('${func.returnType} ${func.name}($argSignature);');
}
- });
- indent.addln('');
- indent.write('void ${api.name}Setup(${api.name} api) ');
- indent.scoped('{', '}', () {
- for (Method func in api.methods) {
- indent.write('');
- indent.scoped('{', '}', () {
- indent.writeln('const BasicMessageChannel channel =');
- indent.inc();
- indent.inc();
- indent.writeln(
- 'BasicMessageChannel(\'${makeChannelName(api, func)}\', StandardMessageCodec());');
- indent.dec();
- indent.dec();
- indent.write('channel.setMessageHandler((dynamic message) async ');
- indent.scoped('{', '});', () {
- final String argType = func.argType;
- final String returnType = func.returnType;
+ indent.write('static void setup(${api.name} api) ');
+ indent.scoped('{', '}', () {
+ for (Method func in api.methods) {
+ indent.write('');
+ indent.scoped('{', '}', () {
+ indent.writeln('const BasicMessageChannel channel =');
+ indent.inc();
+ indent.inc();
+ final String channelName = channelNameFunc == null
+ ? makeChannelName(api, func)
+ : channelNameFunc(func);
indent.writeln(
- 'final Map mapMessage = message as Map;');
- String call;
- if (argType == 'void') {
- call = 'api.${func.name}()';
- } else {
+ 'BasicMessageChannel(\'$channelName\', StandardMessageCodec());');
+ indent.dec();
+ indent.dec();
+ final String messageHandlerSetter =
+ isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler';
+ indent
+ .write('channel.$messageHandlerSetter((dynamic message) async ');
+ indent.scoped('{', '});', () {
+ final String argType = func.argType;
+ final String returnType = func.returnType;
indent.writeln(
- 'final $argType input = $argType._fromMap(mapMessage);');
- call = 'api.${func.name}(input)';
- }
- if (returnType == 'void') {
- indent.writeln('$call;');
- } else {
- indent.writeln('final $returnType output = $call;');
- indent.writeln('return output._toMap();');
- }
+ 'final Map mapMessage = message as Map;');
+ String call;
+ if (argType == 'void') {
+ call = 'api.${func.name}()';
+ } else {
+ indent.writeln(
+ 'final $argType input = $argType._fromMap(mapMessage);');
+ call = 'api.${func.name}(input)';
+ }
+ if (returnType == 'void') {
+ indent.writeln('$call;');
+ if (isMockHandler) {
+ indent.writeln('return {};');
+ }
+ } else {
+ indent.writeln('final $returnType output = $call;');
+ const String returnExpresion = 'output._toMap()';
+ final String returnStatement = isMockHandler
+ ? 'return {\'${Keys.result}\': $returnExpresion};'
+ : 'return $returnExpresion;';
+ indent.writeln(returnStatement);
+ }
+ });
});
- });
- }
+ }
+ });
});
+ indent.addln('');
}
/// Generates Dart source code for the given AST represented by [root],
@@ -166,6 +180,15 @@ void generateDart(Root root, StringSink sink) {
for (Api api in root.apis) {
if (api.location == ApiLocation.host) {
_writeHostApi(indent, api);
+ if (api.dartHostTestHandler != null) {
+ final Api mockApi = Api(
+ name: api.dartHostTestHandler,
+ methods: api.methods,
+ location: ApiLocation.flutter);
+ _writeFlutterApi(indent, mockApi,
+ channelNameFunc: (Method func) => makeChannelName(api, func),
+ isMockHandler: true);
+ }
} else if (api.location == ApiLocation.flutter) {
_writeFlutterApi(indent, api);
}
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 57d8340368c7..5df2840ce140 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -8,7 +8,7 @@ import 'dart:mirrors';
import 'ast.dart';
/// The current version of pigeon.
-const String pigeonVersion = '0.1.0-experimental.11';
+const String pigeonVersion = '0.1.0';
/// Read all the content from [stdin] to a String.
String readStdin() {
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index 2f55ec697010..dd8dbb953e5f 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -90,7 +90,7 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
for (Class klass in root.classes) {
indent.writeln(
- '@interface ${_className(options.prefix, klass.name)} : NSObject ');
+ '@interface ${_className(options.prefix, klass.name)} : NSObject');
for (Field field in klass.fields) {
final HostDatatype hostDatatype = getHostDatatype(
field, root.classes, _objcTypeForDartType,
@@ -115,20 +115,20 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
final String returnTypeName =
_className(options.prefix, func.returnType);
final String returnType =
- func.returnType == 'void' ? 'void' : '$returnTypeName *';
+ func.returnType == 'void' ? 'void' : 'nullable $returnTypeName *';
if (func.argType == 'void') {
indent.writeln(
- '-($returnType)${func.name}:(FlutterError * _Nullable * _Nonnull)error;');
+ '-($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;');
+ '-($returnType)${func.name}:($argType*)input error:(FlutterError *_Nullable *_Nonnull)error;');
}
}
indent.writeln('@end');
indent.writeln('');
indent.writeln(
- 'extern void ${apiName}Setup(id binaryMessenger, id<$apiName> api);');
+ 'extern void ${apiName}Setup(id binaryMessenger, id<$apiName> _Nullable api);');
indent.writeln('');
} else if (api.location == ApiLocation.flutter) {
indent.writeln('@interface $apiName : NSObject');
diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart
new file mode 100644
index 000000000000..7e4ba0057a17
--- /dev/null
+++ b/packages/pigeon/lib/pigeon.dart
@@ -0,0 +1 @@
+export 'pigeon_lib.dart';
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index ff695ac193dd..f91828d2cc65 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -27,13 +27,35 @@ const List _validTypes = [
'Map',
];
-/// Metadata to mark an API which will be implemented on the host platform.
+/// Metadata to annotate a Pigeon API implemented by the host-platform.
+///
+/// The abstract class with this annotation groups a collection of Dart↔host
+/// interop methods. These methods are invoked by Dart and are received by a
+/// host-platform (such as in Android or iOS) by a class implementing the
+/// generated host-platform interface.
class HostApi {
/// Parametric constructor for [HostApi].
- const HostApi();
+ const HostApi({this.dartHostTestHandler});
+
+ /// The name of an interface generated next to the [HostApi] class. Implement
+ /// this interface and invoke `[name of this handler].setup` to receive calls
+ /// from your real [HostApi] class in Dart instead of the host platform code,
+ /// as is typical.
+ ///
+ /// Prefer to use a mock of the real [HostApi] with a mocking library for unit
+ /// tests. Generating this Dart handler is sometimes useful in integration
+ /// testing.
+ ///
+ /// Defaults to `null` in which case no handler will be generated.
+ final String dartHostTestHandler;
}
-/// Metadata to mark an API which will be implemented in Flutter.
+/// Metadata to annotate a Pigeon API implemented by Flutter.
+///
+/// The abstract class with this annotation groups a collection of Dart↔host
+/// interop methods. These methods are invoked by the host-platform (such as in
+/// Android or iOS) and are received by Flutter by a class implementing the
+/// generated Dart interface.
class FlutterApi {
/// Parametric constructor for [FlutterApi].
const FlutterApi();
@@ -56,16 +78,16 @@ class Error {
bool _isApi(ClassMirror classMirror) {
return classMirror.isAbstract &&
- (_isHostApi(classMirror) || _isFlutterApi(classMirror));
+ (_getHostApi(classMirror) != null || _isFlutterApi(classMirror));
}
-bool _isHostApi(ClassMirror apiMirror) {
+HostApi _getHostApi(ClassMirror apiMirror) {
for (InstanceMirror instance in apiMirror.metadata) {
if (instance.reflectee is HostApi) {
- return true;
+ return instance.reflectee;
}
}
- return false;
+ return null;
}
bool _isFlutterApi(ClassMirror apiMirror) {
@@ -180,11 +202,12 @@ class Pigeon {
MirrorSystem.getName(declaration.returnType.simpleName));
}
}
- root.apis.add(Api()
- ..name = MirrorSystem.getName(apiMirror.simpleName)
- ..location =
- _isHostApi(apiMirror) ? ApiLocation.host : ApiLocation.flutter
- ..methods = functions);
+ final HostApi hostApi = _getHostApi(apiMirror);
+ root.apis.add(Api(
+ name: MirrorSystem.getName(apiMirror.simpleName),
+ location: hostApi != null ? ApiLocation.host : ApiLocation.flutter,
+ methods: functions,
+ dartHostTestHandler: hostApi?.dartHostTestHandler));
}
final List validateErrors = _validateAst(root);
diff --git a/packages/pigeon/mock_handler_tester/.gitignore b/packages/pigeon/mock_handler_tester/.gitignore
new file mode 100644
index 000000000000..f3c205341e7d
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Exceptions to above rules.
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/packages/pigeon/mock_handler_tester/.metadata b/packages/pigeon/mock_handler_tester/.metadata
new file mode 100644
index 000000000000..bb4f09ae7510
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: e6b697a9df5e9ce933024be3334e86b599c60e71
+ channel: unknown
+
+project_type: app
diff --git a/packages/pigeon/mock_handler_tester/README.md b/packages/pigeon/mock_handler_tester/README.md
new file mode 100644
index 000000000000..698b50651175
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/README.md
@@ -0,0 +1,3 @@
+# mock_handler_tester
+
+A test bed for testing the code generated by `dartHostTestHandler`.
diff --git a/packages/pigeon/mock_handler_tester/lib/main.dart b/packages/pigeon/mock_handler_tester/lib/main.dart
new file mode 100644
index 000000000000..e4b5c93dede1
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/lib/main.dart
@@ -0,0 +1,5 @@
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(Container());
+}
diff --git a/packages/pigeon/mock_handler_tester/pubspec.yaml b/packages/pigeon/mock_handler_tester/pubspec.yaml
new file mode 100644
index 000000000000..402c95805539
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/pubspec.yaml
@@ -0,0 +1,19 @@
+name: mock_handler_tester
+description: A testbed for testing dartHostTestHandler.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+version: 1.0.0+1
+
+environment:
+ sdk: ">=2.7.0 <3.0.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ cupertino_icons: ^0.1.3
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+flutter:
+ uses-material-design: true
diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart
new file mode 100644
index 000000000000..5c76d82f0221
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/test/message.dart
@@ -0,0 +1,147 @@
+// Autogenerated from Pigeon (v0.1.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
+import 'dart:async';
+import 'package:flutter/services.dart';
+
+class SearchReply {
+ String result;
+ String error;
+ // ignore: unused_element
+ Map _toMap() {
+ final Map pigeonMap = {};
+ pigeonMap['result'] = result;
+ pigeonMap['error'] = error;
+ return pigeonMap;
+ }
+
+ // ignore: unused_element
+ static SearchReply _fromMap(Map pigeonMap) {
+ final SearchReply result = SearchReply();
+ result.result = pigeonMap['result'];
+ result.error = pigeonMap['error'];
+ return result;
+ }
+}
+
+class SearchRequest {
+ String query;
+ int anInt;
+ bool aBool;
+ // ignore: unused_element
+ Map _toMap() {
+ final Map pigeonMap = {};
+ pigeonMap['query'] = query;
+ pigeonMap['anInt'] = anInt;
+ pigeonMap['aBool'] = aBool;
+ return pigeonMap;
+ }
+
+ // ignore: unused_element
+ static SearchRequest _fromMap(Map pigeonMap) {
+ final SearchRequest result = SearchRequest();
+ result.query = pigeonMap['query'];
+ result.anInt = pigeonMap['anInt'];
+ result.aBool = pigeonMap['aBool'];
+ return result;
+ }
+}
+
+class Nested {
+ SearchRequest request;
+ // ignore: unused_element
+ Map _toMap() {
+ final Map pigeonMap = {};
+ pigeonMap['request'] = request._toMap();
+ return pigeonMap;
+ }
+
+ // ignore: unused_element
+ static Nested _fromMap(Map pigeonMap) {
+ final Nested result = Nested();
+ result.request = SearchRequest._fromMap(pigeonMap['request']);
+ return result;
+ }
+}
+
+abstract class FlutterSearchApi {
+ SearchReply search(SearchRequest arg);
+ static void setup(FlutterSearchApi api) {
+ {
+ const BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.FlutterSearchApi.search', StandardMessageCodec());
+ channel.setMessageHandler((dynamic message) async {
+ final Map mapMessage =
+ message as Map;
+ final SearchRequest input = SearchRequest._fromMap(mapMessage);
+ final SearchReply output = api.search(input);
+ return output._toMap();
+ });
+ }
+ }
+}
+
+class NestedApi {
+ Future search(Nested arg) async {
+ final Map requestMap = arg._toMap();
+ const BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.NestedApi.search', StandardMessageCodec());
+
+ final Map replyMap = await channel.send(requestMap);
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null);
+ } else if (replyMap['error'] != null) {
+ final Map error = replyMap['error'];
+ throw PlatformException(
+ code: error['code'],
+ message: error['message'],
+ details: error['details']);
+ } else {
+ return SearchReply._fromMap(replyMap['result']);
+ }
+ }
+}
+
+class Api {
+ Future search(SearchRequest arg) async {
+ final Map requestMap = arg._toMap();
+ const BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.Api.search', StandardMessageCodec());
+
+ final Map replyMap = await channel.send(requestMap);
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null);
+ } else if (replyMap['error'] != null) {
+ final Map error = replyMap['error'];
+ throw PlatformException(
+ code: error['code'],
+ message: error['message'],
+ details: error['details']);
+ } else {
+ return SearchReply._fromMap(replyMap['result']);
+ }
+ }
+}
+
+abstract class TestHostApi {
+ SearchReply search(SearchRequest arg);
+ static void setup(TestHostApi api) {
+ {
+ const BasicMessageChannel channel = BasicMessageChannel(
+ 'dev.flutter.pigeon.Api.search', StandardMessageCodec());
+ channel.setMockMessageHandler((dynamic message) async {
+ final Map mapMessage =
+ message as Map;
+ final SearchRequest input = SearchRequest._fromMap(mapMessage);
+ final SearchReply output = api.search(input);
+ return {'result': output._toMap()};
+ });
+ }
+ }
+}
diff --git a/packages/pigeon/mock_handler_tester/test/widget_test.dart b/packages/pigeon/mock_handler_tester/test/widget_test.dart
new file mode 100644
index 000000000000..82f652a8ef0a
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/test/widget_test.dart
@@ -0,0 +1,28 @@
+// Copyright 2020 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.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'message.dart';
+
+class Mock implements TestHostApi {
+ bool didCall = false;
+ @override
+ SearchReply search(SearchRequest arg) {
+ didCall = true;
+ return SearchReply()..result = arg.query;
+ }
+}
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ test('description', () async {
+ final Api api = Api();
+ final Mock mock = Mock();
+ TestHostApi.setup(mock);
+ final SearchReply reply = await api.search(SearchRequest()..query = 'foo');
+ expect(mock.didCall, true);
+ expect(reply.result, 'foo');
+ });
+}
diff --git a/packages/pigeon/pigeons/host2flutter.dart b/packages/pigeon/pigeons/host2flutter.dart
index 6d13569d0562..673ff56334f9 100644
--- a/packages/pigeon/pigeons/host2flutter.dart
+++ b/packages/pigeon/pigeons/host2flutter.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:pigeon/pigeon_lib.dart';
+import 'package:pigeon/pigeon.dart';
class SearchRequest {
String query;
diff --git a/packages/pigeon/pigeons/message.dart b/packages/pigeon/pigeons/message.dart
index 955836642ba7..73dc13843ee0 100644
--- a/packages/pigeon/pigeons/message.dart
+++ b/packages/pigeon/pigeons/message.dart
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:pigeon/pigeon_lib.dart';
+// This file is an example pigeon file that is used in compilation, unit, mock
+// handler, and e2e tests.
+
+import 'package:pigeon/pigeon.dart';
class SearchRequest {
String query;
@@ -15,7 +18,7 @@ class SearchReply {
String error;
}
-@HostApi()
+@HostApi(dartHostTestHandler: 'TestHostApi')
abstract class Api {
SearchReply search(SearchRequest request);
}
diff --git a/packages/pigeon/pigeons/void_arg_flutter.dart b/packages/pigeon/pigeons/void_arg_flutter.dart
index 29accee79fd8..9886e0e10c0b 100644
--- a/packages/pigeon/pigeons/void_arg_flutter.dart
+++ b/packages/pigeon/pigeons/void_arg_flutter.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:pigeon/pigeon_lib.dart';
+import 'package:pigeon/pigeon.dart';
class Result {
int code;
diff --git a/packages/pigeon/pigeons/void_arg_host.dart b/packages/pigeon/pigeons/void_arg_host.dart
index 5935dd2aa8f6..38229a08fa70 100644
--- a/packages/pigeon/pigeons/void_arg_host.dart
+++ b/packages/pigeon/pigeons/void_arg_host.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:pigeon/pigeon_lib.dart';
+import 'package:pigeon/pigeon.dart';
class Result {
int code;
diff --git a/packages/pigeon/pigeons/voidflutter.dart b/packages/pigeon/pigeons/voidflutter.dart
index 59c77e0f658a..9606c210e8af 100644
--- a/packages/pigeon/pigeons/voidflutter.dart
+++ b/packages/pigeon/pigeons/voidflutter.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:pigeon/pigeon_lib.dart';
+import 'package:pigeon/pigeon.dart';
class SetRequest {
int value;
diff --git a/packages/pigeon/pigeons/voidhost.dart b/packages/pigeon/pigeons/voidhost.dart
index 9769da5dca49..ffd7f01c4db0 100644
--- a/packages/pigeon/pigeons/voidhost.dart
+++ b/packages/pigeon/pigeons/voidhost.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:pigeon/pigeon_lib.dart';
+import 'package:pigeon/pigeon.dart';
class SetRequest {
int value;
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index f3c11e9a3727..b10abb959dbe 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -1,5 +1,5 @@
name: pigeon
-version: 0.1.0-experimental.11
+version: 0.1.0
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:
diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh
index c04296a8d279..9a81105489cf 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -62,6 +62,15 @@ test_pigeon_ios ./pigeons/voidflutter.dart
test_pigeon_ios ./pigeons/void_arg_host.dart
test_pigeon_ios ./pigeons/void_arg_flutter.dart
+pushd $PWD
+pub run pigeon \
+ --input pigeons/message.dart \
+ --dart_out mock_handler_tester/test/message.dart
+dartfmt -w mock_handler_tester/test/message.dart
+cd mock_handler_tester
+flutter test
+popd
+
DARTLE_H="e2e_tests/test_objc/ios/Runner/dartle.h"
DARTLE_M="e2e_tests/test_objc/ios/Runner/dartle.m"
DARTLE_DART="e2e_tests/test_objc/lib/dartle.dart"
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 189744d76317..008914588507 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:pigeon/generator_tools.dart';
import 'package:test/test.dart';
import 'package:pigeon/dart_generator.dart';
import 'package:pigeon/ast.dart';
@@ -79,7 +80,7 @@ void main() {
generateDart(root, sink);
final String code = sink.toString();
expect(code, contains('abstract class Api'));
- expect(code, contains('void ApiSetup(Api'));
+ expect(code, contains('static void setup(Api'));
});
test('host void', () {
@@ -149,4 +150,31 @@ void main() {
final String code = sink.toString();
expect(code, matches('channel\.send[(]null[)]'));
});
+
+ test('mock dart handler', () {
+ final Root root = Root(apis: [
+ Api(
+ name: 'Api',
+ location: ApiLocation.host,
+ dartHostTestHandler: 'ApiMock',
+ methods: [
+ Method(name: 'doSomething', argType: 'Input', returnType: 'Output'),
+ Method(name: 'voidReturner', argType: 'Input', returnType: 'void')
+ ])
+ ], classes: [
+ Class(
+ name: 'Input',
+ fields: [Field(name: 'input', dataType: 'String')]),
+ Class(
+ name: 'Output',
+ fields: [Field(name: 'output', dataType: 'String')])
+ ]);
+ final StringBuffer sink = StringBuffer();
+ generateDart(root, sink);
+ final String code = sink.toString();
+ expect(code, matches('abstract class ApiMock'));
+ expect(code, isNot(matches('\.ApiMock\.doSomething')));
+ expect(code, matches('\'${Keys.result}\': output._toMap()'));
+ expect(code, contains('return {};'));
+ });
}
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index aba0ed624eda..939bed1f530a 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -52,8 +52,8 @@ void main() {
expect(code, contains('@interface Input'));
expect(code, contains('@interface Output'));
expect(code, contains('@protocol Api'));
- expect(code, matches('Output.*doSomething.*Input.*FlutterError'));
- expect(code, contains('ApiSetup('));
+ expect(code, matches('nullable Output.*doSomething.*Input.*FlutterError'));
+ expect(code, matches('ApiSetup.*\.*_Nullable'));
});
test('gen one api source', () {
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 48f84e50cf82..6772a4e7a58a 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -48,6 +48,11 @@ abstract class VoidArgApi {
Output1 doit();
}
+@HostApi(dartHostTestHandler: 'ApiWithMockDartClassMock')
+abstract class ApiWithMockDartClass {
+ Output1 doit();
+}
+
void main() {
test('parse args - input', () {
final PigeonOptions opts =
@@ -176,4 +181,13 @@ void main() {
expect(results.root.apis[0].methods[0].returnType, equals('Output1'));
expect(results.root.apis[0].methods[0].argType, equals('void'));
});
+
+ test('mockDartClass', () {
+ final Pigeon pigeon = Pigeon.setup();
+ final ParseResults results = pigeon.parse([ApiWithMockDartClass]);
+ expect(results.errors.length, equals(0));
+ expect(results.root.apis.length, equals(1));
+ expect(results.root.apis[0].dartHostTestHandler,
+ equals('ApiWithMockDartClassMock'));
+ });
}