【Flutter】Pigeon を使ってネイティブコードを型安全に呼ぶ


## はじめに
先日の、 [ Flutter 1.20 のリリース記事 ](https://medium.com/flutter/announcing-flutter-1-20-2aaf68c89c75)で、[ Pigeon ](https://pub.dev/packages/pigeon) というパッケージの紹介がありました。
本来 Flutter 側からネイティブコードを呼ぶには、関数名や引数などを文字列ベースで合わせる必要があるなど、少し大変ですが、このパッケージを使うことで、

* ネイティブ側と__型安全に通信__ 
* 自動生成よる__手書きコード量の削減__

が可能になります。

本投稿では、このパッケージを使って、__swift / Kotlin で実装した単純な add メソッド__を Flutter 側 から呼ぶ方法を見ていきます。サンプルプロジェクトは[こちら](https://github.com/nskhei/flutter_pigeon_practice)で公開しています。

## Pigeon が行うこと
Dart 側で定義した、引数や戻り値の情報を元に、__Java / Objective-C のインターフェースやプロトコルを自動生成__します。
ネイティブ側ではこれらを元に実装を行うことで、Flutter 側と型安全に通信することができるようになります。
イメージとしては、 TypeScript で型定義ファイルを作るのに近いかもしれません。

## インストール

```pubspec.yaml
dev_dependencies:
  pigeon: ^0.1.4
```

## Dart 側
まずは、ネイティブと通信するスキーマを定義した dart ファイルを作ります。
自分の場合はプロジェクトルートに`pigeon/`というフォルダを作ってその中に置きました。

```schema.dart
import 'package:pigeon/pigeon.dart';

// 引数の定義
class AddRequest {
  int n1;
  int n2;
}

// 戻り値の定義
class AddReply {
  int result;
}

@HostApi()
abstract class Api {
  AddReply add(AddRequest req);
}

// 生成されるファイルの出力先などの設定
void configurePigeon(PigeonOptions opts) {
  opts.dartOut = 'lib/api_generated.dart';
  opts.javaOut = 'android/app/src/main/java/io/flutter/plugins/Pigeon.java';
  opts.javaOptions.package = "io.flutter.plugins";
  opts.objcHeaderOut = 'ios/Runner/Pigeon.h';
  opts.objcSourceOut = 'ios/Runner/Pigeon.m';
  opts.objcOptions.prefix = 'FLT';
}
```

次に、pigeon コマンドを実行して、必要な Java ファイルなどを生成します。

```bash
flutter pub run pigeon --input pigeon/schema.dart
```

あとは、ネイティブ関数を呼びたい箇所で、Pigeon によって生成された dart ファイルをインポートして使うだけです。

```home_page.dart
import './api_generated.dart';

void callNativeAdd () async {
  final api = Api();
  final req = AddRequest()
       ..n1 = 10
       ..n2 = 20;
  final reply = await api.add(req);
  print(reply.result); // prints 30
}
```

## Kotlin 側
Pigeon によって__生成された Java ファイルの中に Api のインターフェースが書かれている__ので、これを実装したクラスを作って、setup メソッドに渡します。


```MainActivity.kt
class MainActivity: FlutterActivity() {
    // 1. 自動生成されたApiインターフェースを実装したクラスを作る
    private class MyApi: Pigeon.Api {
        override fun add(arg: Pigeon.AddRequest): Pigeon.AddReply {
            val reply = Pigeon.AddReply()
            reply.result = arg.n1 + arg.n2
            return reply
        }
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // 2. setup()を呼ぶ
        Pigeon.Api.setup(flutterEngine.dartExecutor.binaryMessenger, MyApi())
    }
}
```

自分の場合は kotlin から Java 側の static なメソッドは呼べないよというエラーが出ましたが、エラーメッセージにしたがって build.gradle に下記を追加することで動きました。

``` app/build.gradle
android {
  ...
  kotlinOptions {
      jvmTarget = '1.8'
  } 
}
```

## Swift 側
Pigeon によって__生成された Objective-C ファイルを Swift 側から参照できるようにする__ため、`ios/Runner` 内にある `Runner-Bridging-Header.h` にインポート文を追加します。([参考](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift))

```ios/Runner/Runner-Bridging-Header.h
#import "Pigeon.h"
```

__生成されたファイルには、 Api のプロトコルが書かれている__ので、これを実装したクラスを作ります。
専用のファイルを作っても良いですし、`AppDelegate.swift` 内にベタ書きでも OK です。

```swift
 class MyApi: FLTApi {
     func add(_ input: FLTAddRequest, error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> FLTAddReply? {
         let reply = FLTAddReply()
         let result = input.n1!.intValue + input.n2!.intValue
         reply.result = NSNumber.init(value: result)
         return reply
     }
 }
```

あとは、`AppDelegate` 内で Api をインスタンス化して setup に渡すだけです。

```AppDelegate.swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

     // Setupを呼ぶ
    FLTApiSetup(controller.binaryMessenger, MyApi())
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
```

## まとめ
* 今まで[ MethodChannel ](https://api.flutter.dev/flutter/services/MethodChannel-class.html)を使って書いていたネイティブとの接続部分を自動生成してくれる。( 厳密にはPigeon では[ BasicMessageChannel ](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html)が使われてる)

* 事前にスキーマを定義するので、ネイティブと__型安全に通信__できる

* 生成されるコードは、Java / Objective-Cだが、Kotlin からは Java が、Swift からは Objective-C が呼べるので、__普通に使える__。

* __Dart 側からネイティブのコードを意識しなくて良い__ (自動生成されたAPIを呼ぶだけ)

* __ネイティブ側から Dart のコードを意識しなくて良い__ (自動生成されたインターフェースを実装するだけ)

* 公式の[ vider_player ](https://github.com/flutter/plugins/tree/master/packages/video_player/video_player)プラグインで実際に使われていたり、[ Flutter 1.20 のリリース記事 ](https://medium.com/flutter/announcing-flutter-1-20-2aaf68c89c75)で紹介されていたりするので、今後広まっていくかも。