Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pigeon] Updates README.md with examples of using @ProxyApi #8087

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 22.6.2

* Updates `README.md` with examples of using `@ProxyApi`.

## 22.6.1

* [gobject] Moves class declarations to the header to work around a bug in some
Expand Down
16 changes: 16 additions & 0 deletions packages/pigeon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ but reversed. For more information look at the annotation `@FlutterApi()` which
denotes APIs that live in Flutter but are invoked from the host platform.
[Example](./example/README.md#FlutterApi_Example).

### Wrapping a Native API with ProxyApis

**Supported Languages: Kotlin, Swift**

Pigeon supports wrapping a native API with the `@ProxyApi` annotation. For each native class/type
this annotation generates

* A Dart class that acts as a proxy to the native class/type.
* A native language API that handles creating native instances, methods calls from Dart to host,
and method calls from host to Dart.
* A Dart and native language `InstanceManager` that automatically handles garbage collection of the
native instance.
* A native language registrar for setting up all the native language APIs.

See this [example](./example/README.md#Simple_ProxyApi_Example) for example usage.

## Feedback

File an issue in [flutter/flutter](https://github.com/flutter/flutter) with
Expand Down
262 changes: 260 additions & 2 deletions packages/pigeon/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ private class PigeonFlutterApi {
aString aStringArg: String?, completion: @escaping (Result<String, Error>) -> Void
) {
flutterAPI.flutterMethod(aString: aStringArg) {
completion(.success($0))
completion(.success(try! $0.get()))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code didn't compile until I fixed this. I'm assuming our CI doesn't verify the example compiles. Is this unintentional?

I also had to fix the same line for Kotlin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just ran into this in my pr as well. Not sure why the examples are getting compiled in ci though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8731158450509496993/+/u/Run_package_tests/build_examples/stdout

============================================================
|| Running for packages/pigeon [@0:00]
============================================================

Building for: Android

Skipping Android for pigeon/example; not supported.
SKIPPING: No examples found supporting requested platform(s).

O.o

Let me look into what's going on here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it's because the actual example is example/app/, not example/, but example.pubspec.yaml exists so the tooling sees that example is a package.

I'll play with fixing this in a separate PR. I think the pubspec may have only been there for the old excerpt system.

}
}
}
Expand All @@ -303,7 +303,7 @@ private class PigeonFlutterApi {
}

fun callFlutterMethod(aString: String, callback: (Result<String>) -> Unit) {
flutterApi!!.flutterMethod(aString) { echo -> callback(Result.success(echo)) }
flutterApi!!.flutterMethod(aString) { echo -> callback(Result.success(echo.getOrNull()!!)) }
}
}
```
Expand Down Expand Up @@ -351,6 +351,264 @@ pigeon_example_package_message_flutter_api_flutter_method(
self->flutter_api, "hello", nullptr, flutter_method_cb, self);
```

## Simple ProxyApi Example

This example provides a simple overview of how to use Pigeon to wrap a native class.

### Example Class

Below are example native classes that will be wrapped by this example. Note that other languages
can be wrapped, but the generated code will only be in the languages shown below.

#### Kotlin

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/ExampleLibrary.kt (simple-proxy-class)"?>
```kotlin
package dev.flutter.pigeon_example_app

open class SimpleExampleNativeClass(val aField: String, private val aParameter: String) {
open fun flutterMethod(aParameter: String) {}

fun hostMethod(aParameter: String): String {
return "aString"
}
}
```

#### Swift

<?code-excerpt "ios/Runner/ExampleLibrary.swift (simple-proxy-class)"?>
```swift
class SimpleExampleNativeClass {
let aField: String

init(aField: String, aParameter: String) {
self.aField = aField
}

open func flutterMethod(aParameter: String) {}

func hostMethod(aParamter: String) -> String {
return "some string"
}
}
```

### Dart input

This class is declared in the pigeon file and defines the generated Dart class and the native
language API. See `@ProxyApi` for setting additional parameters such as importing native libraries,
setting minimum supported API versions, or setting supported platforms.

<?code-excerpt "pigeons/messages.dart (simple-proxy-definition)"?>
```dart
@ProxyApi(
kotlinOptions: KotlinProxyApiOptions(
fullClassName: 'dev.flutter.pigeon_example_app.SimpleExampleNativeClass',
),
swiftOptions: SwiftProxyApiOptions(
name: 'SimpleExampleNativeClass',
),
)
abstract class SimpleExampleNativeClass {
// ignore: avoid_unused_constructor_parameters
SimpleExampleNativeClass(String aParameter);

late String aField;

/// Makes a call from native language to Dart.
late void Function(String aParameter)? flutterMethod;

/// Makes a call from Dart to native language.
String hostMethod(String aParameter);
}
```

### ProxyApi Implementation

Below is an example of implementing the generated API in the host language.

#### Kotlin

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/ProxyApiImpl.kt (simple-proxy-api)"?>
```kotlin
class SimpleExampleNativeClassProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
PigeonApiSimpleExampleNativeClass(pigeonRegistrar) {

internal class SimpleExampleNativeClassImpl(
val api: SimpleExampleNativeClassProxyApi,
aField: String,
aParameter: String
) : SimpleExampleNativeClass(aField, aParameter) {
override fun flutterMethod(aParameter: String) {
api.flutterMethod(this, aParameter) {}
}
}

override fun pigeon_defaultConstructor(
aField: String,
aParameter: String
): SimpleExampleNativeClass {
return SimpleExampleNativeClassImpl(this, aParameter, aField)
}

override fun aField(pigeon_instance: SimpleExampleNativeClass): String {
return pigeon_instance.aField
}

override fun hostMethod(pigeon_instance: SimpleExampleNativeClass, aParameter: String): String {
return pigeon_instance.hostMethod(aParameter)
}
}
```

#### Swift

<?code-excerpt "ios/Runner/ProxyAPIImpl.swift (simple-proxy-api)"?>
```swift
class SimpleExampleNativeClassImpl: SimpleExampleNativeClass {
let api: PigeonApiSimpleExampleNativeClass

init(api: PigeonApiSimpleExampleNativeClass, aField: String, aParameter: String) {
self.api = api
super.init(aField: aField, aParameter: aField)
}

override func flutterMethod(aParameter: String) {
api.flutterMethod(pigeonInstance: self, aParameter: aParameter) { _ in }
}
}

class SimpleExampleNativeClassAPIDelegate: PigeonApiDelegateSimpleExampleNativeClass {
func pigeonDefaultConstructor(
pigeonApi: PigeonApiSimpleExampleNativeClass, aField: String, aParameter: String
) throws -> SimpleExampleNativeClass {
return SimpleExampleNativeClassImpl(api: pigeonApi, aField: aField, aParameter: aParameter)
}

func aField(
pigeonApi: PigeonApiSimpleExampleNativeClass, pigeonInstance: SimpleExampleNativeClass
) throws -> String {
return pigeonInstance.aField
}

func hostMethod(
pigeonApi: PigeonApiSimpleExampleNativeClass, pigeonInstance: SimpleExampleNativeClass,
aParameter: String
) throws -> String {
return pigeonInstance.hostMethod(aParamter: aParameter)
}
}
```

### Registrar Implementation and Setup

Below is an example of implementing the ProxyApi registrar or registrar delegate in the host
language. Followed by an example of setting up the registrar.

#### Kotlin

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/ProxyApiImpl.kt (simple-proxy-registrar)"?>
```kotlin
open class ProxyApiRegistrar(binaryMessenger: BinaryMessenger) :
MessagesPigeonProxyApiRegistrar(binaryMessenger) {
override fun getPigeonApiSimpleExampleNativeClass(): PigeonApiSimpleExampleNativeClass {
return SimpleExampleNativeClassProxyApi(this)
}
// ···
}
```

<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (registrar-setup)"?>
```kotlin
val registrar = ProxyApiRegistrar(flutterEngine.dartExecutor.binaryMessenger)
registrar.setUp()
```

#### Swift

<?code-excerpt "ios/Runner/ProxyAPIImpl.swift (simple-proxy-registrar)"?>
```swift
open class ProxyAPIDelegate: MessagesPigeonProxyApiDelegate {
func pigeonApiSimpleExampleNativeClass(_ registrar: MessagesPigeonProxyApiRegistrar)
-> PigeonApiSimpleExampleNativeClass
{
return PigeonApiSimpleExampleNativeClass(
pigeonRegistrar: registrar, delegate: SimpleExampleNativeClassAPIDelegate())
}
// ···
}
```

<?code-excerpt "ios/Runner/AppDelegate.swift (registrar-setup)"?>
```swift
proxyApiRegistrar = MessagesPigeonProxyApiRegistrar(
binaryMessenger: controller.binaryMessenger, apiDelegate: ProxyAPIDelegate())
proxyApiRegistrar?.setUp()
```

### Generated Dart Class Usage

Below is an example of using the generated Dart class to call a method and set a callback method.

<?code-excerpt "lib/main.dart (simple-proxy-class)"?>
```dart
final SimpleExampleNativeClass instance = SimpleExampleNativeClass(
aField: 'my field',
aParameter: 'my parameter',
flutterMethod: (SimpleExampleNativeClass instance, String aParameter) {
debugPrint(aParameter);
},
);

debugPrint(instance.aField);
debugPrint(await instance.hostMethod('my parameter'));
```

## Complex ProxyApi Example

Example with inheritance, static methods, and attached fields.

### Dart input

<?code-excerpt "pigeons/messages.dart (complex-proxy-definition)"?>
```dart
@ProxyApi(
kotlinOptions: KotlinProxyApiOptions(
fullClassName: 'dev.flutter.pigeon_example_app.ComplexExampleNativeClass',
),
)
abstract class ComplexExampleNativeClass extends ExampleNativeSuperClass
implements ExampleNativeInterface {
@static
late ExampleNativeSuperClass aStaticField;

@attached
late ExampleNativeSuperClass anAttachedField;

@static
String staticHostMethod();
}

@ProxyApi(
kotlinOptions: KotlinProxyApiOptions(
fullClassName: 'dev.flutter.pigeon_example_app.ExampleNativeSuperClass',
),
)
abstract class ExampleNativeSuperClass {
String inheritedSuperClassMethod();
}

@ProxyApi(
kotlinOptions: KotlinProxyApiOptions(
fullClassName: 'dev.flutter.pigeon_example_app.ExampleNativeInterface',
),
)
abstract class ExampleNativeInterface {
late void Function()? inheritedInterfaceMethod;
}
```

## Swift / Kotlin Plugin Example

A downloadable example of using Pigeon to create a Flutter Plugin with Swift and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@file:Suppress("unused")

// #docregion simple-proxy-class
package dev.flutter.pigeon_example_app

open class SimpleExampleNativeClass(val aField: String, private val aParameter: String) {
open fun flutterMethod(aParameter: String) {}

fun hostMethod(aParameter: String): String {
return "aString"
}
}
// #enddocregion simple-proxy-class

class ComplexExampleNativeClass : ExampleNativeSuperClass(), ExampleNativeInterface {
val anAttachedField = ExampleNativeSuperClass()

companion object {
val aStaticField = ExampleNativeSuperClass()

fun staticHostMethod(): String {
return "some string"
}
}

override fun inheritedInterfaceMethod() {}
}

open class ExampleNativeSuperClass {
fun inheritedSuperClassMethod(): String {
return "some string"
}
}

interface ExampleNativeInterface {
fun inheritedInterfaceMethod()
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private class PigeonFlutterApi {
}

fun callFlutterMethod(aString: String, callback: (Result<String>) -> Unit) {
flutterApi!!.flutterMethod(aString) { echo -> callback(Result.success(echo)) }
flutterApi!!.flutterMethod(aString) { echo -> callback(Result.success(echo.getOrNull()!!)) }
}
}
// #enddocregion kotlin-class-flutter
Expand All @@ -57,5 +57,10 @@ class MainActivity : FlutterActivity() {

val api = PigeonApiImplementation()
ExampleHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api)

// #docregion registrar-setup
val registrar = ProxyApiRegistrar(flutterEngine.dartExecutor.binaryMessenger)
registrar.setUp()
// #enddocregion registrar-setup
}
}
Loading