Skip to content

Commit

Permalink
[pigeon] implemented java host to flutter calls and updated documenta…
Browse files Browse the repository at this point in the history
…tion (flutter#101)
  • Loading branch information
gaaclarke authored Feb 21, 2020
1 parent 5e1e240 commit 8c3ab50
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 87 deletions.
2 changes: 1 addition & 1 deletion packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## 0.1.0-experimental.3

* Added Flutter->Host calls for Android Java.
* Added support for for Android Java.

## 0.1.0-experimental.2

Expand Down
100 changes: 23 additions & 77 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,40 @@ host platform type-safe and easier.

## Supported Platforms

Currently Pigeon only supports generating Objective-C code for usage on iOS and calling host functions from Flutter.
Currently Pigeon only supports generating Objective-C code for usage on iOS and
Java code for Android.

## Runtime Requirements

Pigeon generates all the code that is needed to communicate between Flutter and the host platform, there is no extra runtime requirement. A plugin author doesn't need to worry about conflicting versions of Pigeon.
Pigeon generates all the code that is needed to communicate between Flutter and
the host platform, there is no extra runtime requirement. A plugin author
doesn't need to worry about conflicting versions of Pigeon.

## Usage

### Steps
### Flutter calling into iOS Steps

1) Add Pigeon as a dev_dependency.
1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
1) Run pigeon on your ".dart" file to generate the required Dart and Objective-C code.
1) Add the generated code to your `ios/Runner.xcworkspace` XCode project for compilation.
1) Add the generated Dart code to `lib` for compilation.
1) Add the generated Objective-C code to your Xcode project for compilation
(e.g. `ios/Runner.xcworkspace`).
1) Implement the generated iOS protocol for handling the calls on iOS, set it up
as the handler for the messages.
1) Call the generated Dart methods.

### Flutter calling into Android Steps

1) Add Pigeon as a dev_dependency.
1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
1) Run pigeon on your ".dart" file to generate the required Dart and Java code.
1) Add the generated Dart code to `./lib` for compilation.
1) Add the generated Java code to your `./android/app/src/main/java` directory for compilation.
1) Implement the generated Java interface for handling the calls on Android, set it up
as the handler for the messages.
1) Call the generated Dart methods.

### Rules for defining your communication interface

1) The file should contain no methods or function definitions.
Expand All @@ -37,79 +53,9 @@ Pigeon generates all the code that is needed to communicate between Flutter and
1) Method declarations on the Api classes should have one argument and a return
value whose types are defined in the file.

### Example

#### message.dart

```dart
import 'package:pigeon/pigeon_lib.dart';
class SearchRequest {
String query;
}
class SearchReply {
String result;
}
@HostApi()
abstract class Api {
SearchReply search(SearchRequest request);
}
```

#### invocation

```sh
pub run pigeon \
--input pigeons/message.dart \
--dart_out lib/pigeon.dart \
--objc_header_out ios/Runner/pigeon.h \
--objc_source_out ios/Runner/pigeon.m
```

#### AppDelegate.m

```objc
#import "AppDelegate.h"
#import <Flutter/Flutter.h>
#import "pigeon.h"

@interface MyApi : NSObject <Api>
@end

@implementation MyApi
-(SearchReply*)search:(SearchRequest*)request {
SearchReply *reply = [[SearchReply alloc] init];
reply.result =
[NSString stringWithFormat:@"Hi %@!", request.query];
return reply;
}
@end

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
MyApi *api = [[MyApi alloc] init];
ApiSetup(getFlutterEngine().binaryMessenger, api);
return YES;
}
```
#### test.dart
```dart
import 'pigeon.dart';
void main() {
testWidgets("test pigeon", (WidgetTester tester) async {
SearchRequest request = SearchRequest()..query = "Aaron";
Api api = Api();
SearchReply reply = await api.search(request);
expect(reply.result, equals("Hi Aaron!"));
});
}
```
## Example

See the "Example" tab.

## Supported Datatypes

Expand Down
96 changes: 96 additions & 0 deletions packages/pigeon/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Pigeon iOS / Android Example

## message.dart

```dart
import 'package:pigeon/pigeon_lib.dart';
class SearchRequest {
String query;
}
class SearchReply {
String result;
}
@HostApi()
abstract class Api {
SearchReply search(SearchRequest request);
}
```

## invocation

```sh
flutter pub run pigeon \
--input pigeons/message.dart \
--dart_out lib/pigeon.dart \
--objc_header_out ios/Runner/pigeon.h \
--objc_source_out ios/Runner/pigeon.m \
--java_out ./android/app/src/main/java/dev/flutter/pigeon/Pigeon.java \
--java_package "dev.flutter.pigeon"
```

## AppDelegate.m

```objc
#import "AppDelegate.h"
#import <Flutter/Flutter.h>
#import "pigeon.h"

@interface MyApi : NSObject <Api>
@end

@implementation MyApi
-(SearchReply*)search:(SearchRequest*)request {
SearchReply *reply = [[SearchReply alloc] init];
reply.result =
[NSString stringWithFormat:@"Hi %@!", request.query];
return reply;
}
@end

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
MyApi *api = [[MyApi alloc] init];
ApiSetup(getFlutterEngine().binaryMessenger, api);
return YES;
}
```
## StartActivity.java
```java
import dev.flutter.pigeon.Pigeon;
public class StartActivity extends Activity {
private class MyApi extends Pigeon.Api {
Pigeon.SearchReply search(Pigeon.SearchRequest request) {
Pigeon.SearchReply reply = new Pigeon.SearchReply();
reply.result = String.format("Hi %s!", request.query);
return reply;
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Pigeon.SetupApi(getBinaryMessenger(), new MyApi());
}
}
```

## test.dart

```dart
import 'pigeon.dart';
void main() {
testWidgets("test pigeon", (WidgetTester tester) async {
SearchRequest request = SearchRequest()..query = "Aaron";
Api api = Api();
SearchReply reply = await api.search(request);
expect(reply.result, equals("Hi Aaron!"));
});
}
```
3 changes: 2 additions & 1 deletion packages/pigeon/lib/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ void generateDart(Root root, StringSink sink) {
final List<String> customClassNames =
root.classes.map((Class x) => x.name).toList();
final Indent indent = Indent(sink);
indent.writeln('// Autogenerated from Dartle, do not edit directly.');
indent.writeln('// $generatedCodeWarning');
indent.writeln('// $seeAlsoWarning');
indent.writeln('import \'package:flutter/services.dart\';');
indent.writeln('');

Expand Down
9 changes: 8 additions & 1 deletion packages/pigeon/lib/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Indent {

/// Create the generated channel name for a [func] on a [api].
String makeChannelName(Api api, Method func) {
return 'dev.flutter.dartle.${api.name}.${func.name}';
return 'dev.flutter.pigeon.${api.name}.${func.name}';
}

/// Represents the mapping of a Dart datatype to a Host datatype.
Expand Down Expand Up @@ -116,3 +116,10 @@ HostDatatype getHostDatatype(
return HostDatatype(datatype: datatype, isBuiltin: true);
}
}

/// Warning printed at the top of all generated code.
const String generatedCodeWarning =
'Autogenerated from Pigeon, do not edit directly.';

/// String to be printed after `generatedCodeWarning`.
const String seeAlsoWarning = 'See also: https://pub.dev/packages/pigeon';
52 changes: 46 additions & 6 deletions packages/pigeon/lib/java_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,49 @@ void _writeHostApi(Indent indent, Api api) {
});
}

// void _writeFlutterApi(Indent indent, Api api) {
// assert(api.location == ApiLocation.flutter);
// }
void _writeFlutterApi(Indent indent, Api api) {
assert(api.location == ApiLocation.flutter);
indent.writeln(
'/** Generated class from Pigeon that represents Flutter messages that can be called from Java.*/');
indent.write('public static class ${api.name} ');
indent.scoped('{', '}', () {
indent.writeln('private BinaryMessenger binaryMessenger;');
indent.write('public ${api.name}(BinaryMessenger argBinaryMessenger)');
indent.scoped('{', '}', () {
indent.writeln('this.binaryMessenger = argBinaryMessenger;');
});
indent.write('public interface Reply<T> ');
indent.scoped('{', '}', () {
indent.writeln('void reply(T reply);');
});
for (Method func in api.methods) {
final String channelName = makeChannelName(api, func);
indent.write(
'public void ${func.name}(${func.argType} argInput, Reply<${func.returnType}> callback) ');
indent.scoped('{', '}', () {
indent.writeln('BasicMessageChannel<Object> channel =');
indent.inc();
indent.inc();
indent.writeln(
'new BasicMessageChannel<Object>(binaryMessenger, "$channelName", new StandardMessageCodec());');
indent.dec();
indent.dec();
indent.writeln('HashMap inputMap = argInput.toMap();');
indent.write(
'channel.send(inputMap, new BasicMessageChannel.Reply<Object>() ');
indent.scoped('{', '});', () {
indent.write('public void reply(Object channelReply) ');
indent.scoped('{', '}', () {
indent.writeln('HashMap outputMap = (HashMap)channelReply;');
indent.writeln(
'${func.returnType} output = ${func.returnType}.fromMap(outputMap);');
indent.writeln('callback.reply(output);');
});
});
});
}
});
}

String _makeGetter(Field field) {
final String uppercased =
Expand All @@ -96,8 +136,8 @@ String _javaTypeForDartType(String datatype) {
/// provided [options].
void generateJava(JavaOptions options, Root root, StringSink sink) {
final Indent indent = Indent(sink);
indent.writeln('// Autogenerated from Pigeon, do not edit directly.');
indent.writeln('// See also: https://pub.dev/packages/pigeon');
indent.writeln('// $generatedCodeWarning');
indent.writeln('// $seeAlsoWarning');
indent.addln('');
if (options.package != null) {
indent.writeln('package ${options.package};');
Expand Down Expand Up @@ -157,7 +197,7 @@ void generateJava(JavaOptions options, Root root, StringSink sink) {
if (api.location == ApiLocation.host) {
_writeHostApi(indent, api);
} else if (api.location == ApiLocation.flutter) {
// _writeFlutterApi(indent, api);
_writeFlutterApi(indent, api);
}
}
});
Expand Down
3 changes: 2 additions & 1 deletion packages/pigeon/lib/objc_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ String _propertyTypeForDartType(String type) {
/// provided [options].
void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
final Indent indent = Indent(sink);
indent.writeln('// Autogenerated from Dartle.');
indent.writeln('// $generatedCodeWarning');
indent.writeln('// $seeAlsoWarning');
indent.writeln('#import <Foundation/Foundation.h>');
indent.writeln('@protocol FlutterBinaryMessenger;');
indent.writeln('@class FlutterStandardTypedData;');
Expand Down
4 changes: 4 additions & 0 deletions packages/pigeon/lib/pigeon_lib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'objc_generator.dart';

const List<String> _validTypes = <String>[
'String',
'bool',
'int',
'double',
'Uint8List',
Expand Down Expand Up @@ -204,6 +205,8 @@ options:
..addOption('objc_source_out',
help: 'Path to generated Objective-C source file (.m).')
..addOption('java_out', help: 'Path to generated Java file (.java).')
..addOption('java_package',
help: 'The package that generated Java code will be in.')
..addOption('objc_header_out',
help: 'Path to generated Objective-C header file (.h).')
..addOption('objc_prefix',
Expand All @@ -220,6 +223,7 @@ options:
opts.objcSourceOut = results['objc_source_out'];
opts.objcOptions.prefix = results['objc_prefix'];
opts.javaOut = results['java_out'];
opts.javaOptions.package = results['java_package'];
return opts;
}

Expand Down
22 changes: 22 additions & 0 deletions packages/pigeon/test/java_generator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,26 @@ void main() {
expect(code, contains('private long[] aInt64List;'));
expect(code, contains('private double[] aFloat64List;'));
});

test('gen one flutter api', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'Output')
])
], 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();
final JavaOptions javaOptions = JavaOptions();
javaOptions.className = 'Messages';
generateJava(javaOptions, root, sink);
final String code = sink.toString();
expect(code, contains('public static class Api'));
expect(code, matches('doSomething.*Input.*Output'));
});
}
Loading

0 comments on commit 8c3ab50

Please sign in to comment.