LoginSignup
11
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-08-08

はじめに

先日の、 Flutter 1.20 のリリース記事 で、 Pigeon というパッケージの紹介がありました。
本来 Flutter 側からネイティブコードを呼ぶには、関数名や引数などを文字列ベースで合わせる必要があるなど、少し大変ですが、このパッケージを使うことで、

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

が可能になります。

本投稿では、このパッケージを使って、swift / Kotlin で実装した単純な add メソッドを Flutter 側 から呼ぶ方法を見ていきます。サンプルプロジェクトはこちらで公開しています。

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 ファイルなどを生成します。

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 にインポート文を追加します。(参考)

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 に渡すだけです。

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 を使って書いていたネイティブとの接続部分を自動生成してくれる。( 厳密にはPigeon では BasicMessageChannel が使われてる)

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

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

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

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

  • 公式の vider_player プラグインで実際に使われていたり、 Flutter 1.20 のリリース記事 で紹介されていたりするので、今後広まっていくかも。

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3