Pigeonというdartパッケージを使う機会が現場であったので公式の記載とサンプルについて理解を深めるためまとめた。
現場で「ここはネイティブを呼ぶしかないか」という場面に遭遇するときに役に立てばと思います。
公式

pigeon | Dart package
Code generator tool to make communication between Flutter and the host platform type-safe and easier.
packages/packages/pigeon/example/README.md at main · flutter/packages
A collection of useful packages maintained by the Flutter team - flutter/packages
pigeonとは
- flutterとネイティブの間を安全に繋ぐコードジェネレータ
- カスタムプラットフォームチャンネルの記述がなくなる
- MethodChanelであったタイポなどの心配が減る
pigeonを使うことで、チャンネルメソッドを呼ばなくてもスッキリしたコードでネイティブを呼び出すことが可能になるます。
HostApi/FlutterApiとは
pigeonを使用するにあたって以下の2つのAPIを使ってdart←→ネイティブ(iOS,Android)のやり取りを実施します。
HostApi
- Dart からホスト側(iOS/Android)を呼び出すためのインターフェース
- バッテリー・位置情報などに使える
FlutterApi
- ホスト(iOS/Android)から Dart を呼び出すためのインターフェース
- Push通知が届いた時に Flutter に伝える時などに使える
使い方
実際に簡単なメソッドでpigeonの使い方を見ていきたいと思います。
公式に記載にあったサンプルを参考にし,flutterとswiftのやり取りについて記載しています。
(今回、kotlin,Andorid側には触れていません。)
サポートしているネイティブアプリコード
- Android: Kotlin ,Java
- iOS: Swift,Objective-C
dev_dependencyへの追加
- pubspec.yamlにpgeionを追加
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
pigeon: ^25.5.0生成用のdartファイルの記述
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
dartOptions: DartOptions(),
kotlinOut: 'android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt',
kotlinOptions: KotlinOptions(),
swiftOut: 'ios/Runner/Messages.g.swift',
swiftOptions: SwiftOptions(),
dartPackageName: 'pigeon_example_package',
),
)dart run実行
- 以下コマンド実行
dart run pigeon --input path/to/input.dart- 下記3つのファイルがジェネレートされる
- dart file:
messages.g.dart - kotlin file:
Messages.g.kt - swift file:
Messages.g.swift
- dart file:
HostApi作成
- flutterからswiftを呼ぶためのメソッドを定義
enum Code { one, two }
class MessageData {
MessageData({required this.code, required this.data});
String? name;
String? description;
Code code;
Map<String, String> data;
}
@HostApi()
abstract class ExampleHostApi {
String getHostLanguage();
// 非同期処理を想定するため@asyncを定義
@async
bool sendMessage(MessageData message);
}HostApiを実際に使用する
final ExampleHostApi _api = ExampleHostApi();
/// MessageDataクラス, HostApiを使用してメッセージを送信
Future<bool> sendMessage(String messageText) {
final MessageData message = MessageData(
code: Code.one,
data: <String, String>{'header': 'this is a header'},
description: 'uri text',
);
try {
return _api.sendMessage(message);
} catch (e) {
// エラーハンドリング
return Future<bool>(() => true);
}
}
Swift側でflutterから呼ばれるコードを定義
- 生成されたSwiftコードを利用してflutterから呼ばれるコードを定義
- codeがoneの場合、erorrを吐き、twoならtrueを返す
- FlutterError は Swift.Error が存在しないため、FlutterError の代わりに PigeonError を使用する
private class PigeonApiImplementation: ExampleHostApi {
func getHostLanguage() throws -> String {
return "Swift"
}
func sendMessage(message: MessageData, completion: @escaping (Result<Bool, Error>) -> Void) {
if message.code == Code.one {
completion(.failure(PigeonError(code: "code", message: "message", details: "details")))
return
}
completion(.success(true))
}
}
Swiftが呼ばれるようににsetupメソッドを定義
Message.g(自動生成されたdartファイル)にsetUp関数が生成されるので、AppDelegateのdidFinishLaunchingWithOptionsに定義
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
// ✅ Pigeon API の登録
ExampleHostApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: PigeonApiImplementation())sentMessageをdartで呼んでみる
- future<bool>のため
asyncawaitを使って確認 - 下記部分でoneをtwoと変えればtrueにoneのままで、例外が投げられる(PlatformException)
final MessageData message = MessageData(
code: Code.one,
data: <String, String>{'header': 'this is a header'},
description: 'uri text',
); ElevatedButton(
onPressed: () async {
try {
final value = await sendMessage('Test');
print('Message sent successfully: $value');
} catch (error) {
print('Error sending message: $error');
}
},
child: Text('Button')),- oneの場合
Error sending message: PlatformException(code, message, details, null)
- twoの場合
Message sent successfully: true
getHostLanguageを呼んでみる
- swiftから呼んだ文字列
"Swift"が帰ってくるはず
ElevatedButton(
onPressed: () async {
final hostLanguage = await getHostLanguage();
print('Host language: $hostLanguage');
},
child: Text('Button 2')),FlutterApiを作成
- swiftからflutterを呼ぶための定義を記載
@FlutterApi()
abstract class MessageFlutterApi {
String flutterMethod(String? aString);
}FlutterApiをdartで使用できるように定義
- 生成されたコードを使用し、apiの実装を定義
- 引数の
Stringを返すメソッドを定義(swift側で呼ぶメソッド) - flutter側で使用できるように
setUpメソッド定義
/// ExampleFlutterApiの実装
class _ExampleFlutterApi implements MessageFlutterApi {
@override
String flutterMethod(String? aString) {
// Flutter側の処理を実装
return aString ?? 'test';
}
}
/// ExampleFlutterApiのセットアップ
void setupExampleFlutterApi() {
MessageFlutterApi.setUp(_ExampleFlutterApi());
} swift側で呼び出すための記載
- flutterApiを生成されたコードを使用し設定
- Flutterから戻ってきた結果を非同期で返す
private class PigeonFlutterApi {
var flutterAPI: MessageFlutterApi
init(binaryMessenger: FlutterBinaryMessenger) {
flutterAPI = MessageFlutterApi(binaryMessenger: binaryMessenger)
}
func callFlutterMethod(
aString aStringArg: String?, completion: @escaping (Result<String, PigeonError>) -> Void
) {
flutterAPI.flutterMethod(aString: aStringArg) {
completion($0)
}
}
}
swiftで実際に呼び出してみる
AppDelegateにダミー画面を表示できるよう設定- 上記で作成した
FlutterApiを定義 - 作成した別画面を起動時に表示
- ダミー画面に
AppDelegeteのpigeonFlutterApiを渡し遷移
var pigeonFlutterApi: PigeonFlutterApi?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
// Flutter API の登録
pigeonFlutterApi = PigeonFlutterApi(binaryMessenger: controller.binaryMessenger)
// ネイティブ画面(ダミー ViewController)をすぐ表示してテスト
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let nativeVC = NativeViewController()
nativeVC.pigeonFlutterApi = self.pigeonFlutterApi
controller.present(nativeVC, animated: true, completion: nil)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}ダミー画面
- クローズボタンを仮でUI設定
- aStringの引数にテストのStringの値を入れて結果を返す
- 成功、エラーを吐く場合でprint内容を変更
import UIKit
class NativeViewController: UIViewController {
var pigeonFlutterApi: PigeonFlutterApi?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let closeButton = UIButton(type: .system)
closeButton.setTitle("Close & Notify Flutter", for: .normal)
closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
closeButton.frame = CGRect(x: 50, y: 200, width: 220, height: 44)
view.addSubview(closeButton)
}
@objc func closeButtonTapped() {
pigeonFlutterApi?.callFlutterMethod(aString: "Native screen closed") { result in
switch result {
case .success(let response):
print("Flutter returned: \\(response)")
case .failure(let error):
print("Error: \\(error)")
}
}
// 閉じる処理(画面を閉じたい場合)
dismiss(animated: true, completion: nil)
}
}Xcodeでiosファイルを実行
- swiftがflutterの処理をキャッチできたか確認するためiosファイルを開きXcodeを開く
- ビルドして下記のように表示されていたら成功でflutterの値が返ってきている
- flutterとの通信が成功しflutter側でもエラーは投げていないので
case .successが通る
- flutterとの通信が成功しflutter側でもエラーは投げていないので
Flutter returned: Native screen closedまとめ
pigeonを使うことでネイティブ呼び出しがスッキリするのでぜひ利用してみてください。公式にある記載とサンプルを理解すればある程度の実装は容易かなと思いますので、ぜひ。
ではまた。



コメント