はじめに
先日の、 Flutter 1.20 のリリース記事 で、 Pigeon というパッケージの紹介がありました。
本来 Flutter 側からネイティブコードを呼ぶには、関数名や引数などを文字列ベースで合わせる必要があるなど、少し大変ですが、このパッケージを使うことで、
- ネイティブ側と__型安全に通信__
- 自動生成よる__手書きコード量の削減__
が可能になります。
本投稿では、このパッケージを使って、__swift / Kotlin で実装した単純な add メソッド__を Flutter 側 から呼ぶ方法を見ていきます。サンプルプロジェクトはこちらで公開しています。
Pigeon が行うこと
Dart 側で定義した、引数や戻り値の情報を元に、__Java / Objective-C のインターフェースやプロトコルを自動生成__します。
ネイティブ側ではこれらを元に実装を行うことで、Flutter 側と型安全に通信することができるようになります。
イメージとしては、 TypeScript で型定義ファイルを作るのに近いかもしれません。
インストール
dev_dependencies:
pigeon: ^0.1.4
Dart 側
まずは、ネイティブと通信するスキーマを定義した dart ファイルを作ります。
自分の場合はプロジェクトルートにpigeon/
というフォルダを作ってその中に置きました。
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 ファイルなどを生成します。
flutter pub run pigeon --input pigeon/schema.dart
あとは、ネイティブ関数を呼びたい箇所で、Pigeon によって生成された 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 メソッドに渡します。
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 に下記を追加することで動きました。
android {
...
kotlinOptions {
jvmTarget = '1.8'
}
}
Swift 側
Pigeon によって__生成された Objective-C ファイルを Swift 側から参照できるようにする__ため、ios/Runner
内にある Runner-Bridging-Header.h
にインポート文を追加します。(参考)
#import "Pigeon.h"
__生成されたファイルには、 Api のプロトコルが書かれている__ので、これを実装したクラスを作ります。
専用のファイルを作っても良いですし、AppDelegate.swift
内にベタ書きでも OK です。
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 に渡すだけです。
@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 を使って書いていたネイティブとの接続部分を自動生成してくれる。( 厳密にはPigeon では BasicMessageChannel が使われてる)
-
事前にスキーマを定義するので、ネイティブと__型安全に通信__できる
-
生成されるコードは、Java / Objective-Cだが、Kotlin からは Java が、Swift からは Objective-C が呼べるので、普通に使える。
-
Dart 側からネイティブのコードを意識しなくて良い (自動生成されたAPIを呼ぶだけ)
-
ネイティブ側から Dart のコードを意識しなくて良い (自動生成されたインターフェースを実装するだけ)
-
公式の vider_player プラグインで実際に使われていたり、 Flutter 1.20 のリリース記事 で紹介されていたりするので、今後広まっていくかも。