🐦

【Flutter】初めてのPigeon

2022/02/06に公開

この記事はとりあえずPigeonを使ってiOS側と通信するのを試すものです。Android側は省いています🙇‍♂️

Pigeonって何

Flutterでネイティブ側との通信に使われるMethod Channelという機能があります。
Pigeonは、そのMethod Channelを型安全に行うのためのパッケージです。
まだMethod Channelを触ったことがない方は公式にチュートリアルがあるのでそれをやってから、Pigeonを触ってみることをお勧めします。

Pigeonの読み方

Pigeonは”ピジョン”と読むっぽい。

環境

$ flutter --version
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available!                                     ║
║                                                                            ║
║ To update to the latest version, run "flutter upgrade".                    ║
╚════════════════════════════════════════════════════════════════════════════╝


Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 18116933e7 (4 months ago) • 2021-10-15 10:46:35 -0700
Engine • revision d3ea636dc5
Tools • Dart 2.14.4

始める

とりあえずPigeonの公式のexampleを参考に、より簡易にアレンジしたものを使います。

Pigeonを導入する

pubspec.yaml
dev_dependencies:
  pigeon: ^1.0.17

ネイティブ側とやりとりするクラスを作成する

ルートにpigeonsディレクトリを作成し、その中にmessage.dartファイルを作成する。

message.dart
import 'package:pigeon/pigeon.dart';

class Book {
  String? title;
  String? author;
}

()
abstract class BookApi {
  List<Book?> recommend();
}

pigeonにコードを生成させる

$ flutter pub run pigeon \
  --input pigeons/message.dart \ 
  --dart_out lib/pigeon.dart \
  --objc_header_out ios/Runner/pigeon.h \
  --objc_source_out ios/Runner/pigeon.m
コマンド 意味
--input objective-c, javaに変換する対象のファイル
--dart_out flutter側から使うコードの出力先
--objc_header_out objective-cのheaderに変換されたコードの出力先
--objc_source_out objective-cのsourceに変換されたコードの出力先

上記のコマンドを実行すると、以下の3つのファイルが作成されます。

  • lib/pigeon.dart
  • ios/Runner/pigeon.h
  • ios/Runner/pigeon.m

Flutter側を書く

ボタンをタップするとiOS側で作ったBookの情報を取得してprintするだけの画面

book_page.dart
import 'package:flutter/cupertino.dart';
import 'package:pigeon_tutorial/pigeon.dart';

class BookPage extends StatefulWidget {
  const BookPage({Key? key}) : super(key: key);

  
  State<StatefulWidget> createState() {
    return _BookPageState();
  }
}

class _BookPageState extends State<BookPage> {

  
  Widget build(BuildContext context) {
    return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            CupertinoButton(
              child: const Text('tap!'),
              onPressed: callNativeApi,
            ),
          ],
        ),
    );
  }

  void callNativeApi () async {
    final api = BookApi();
    final result = await api.recommend();
    final book = result[0]!;
    print("${book.title}: ${book.author}");
  }
}

ほぼflutterプロジェクトを立ち上げた状態。
表示する画面を上で作成したBookPageにしている。

main.dart
import 'package:flutter/material.dart';
import 'package:pigeon_tutorial/book_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const BookPage(), // ここを変更した
    );
  }
}
...

iOS側を書く

ファイルを作成し、Flutterk側で作成したBookApiを継承したMyApiを作成する。

MyApi.swift
import Foundation

class MyApi: NSObject, BookApi {
    func recommendWithError(_ error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> [Book]? {
        return [Book.make(withTitle: "タイトル", author: "著者")]
    }
}

ここで注目していただきたいのは、Flutter側で定義したBookApiのrecommendとちょっと関数の名前や引数が違う。
実は、Pigeonが色々調整してくれるようです。
ありがたいですが、どう書いたらいいのかわからないのは困りものです。
ここの書き方は、ios/Runner/pigeon.h をみるとわかる。

pigeon.h
...
@interface Book : NSObject
+ (instancetype)makeWithTitle:(nullable NSString *)title
    author:(nullable NSString *)author;
...
...
@protocol BookApi
- (nullable NSArray<Book *> *)recommendWithError:(FlutterError *_Nullable *_Nonnull)error;
@end
...

ここをみるとどのように対応したら良いかがわかる。

AppDelegate.swiftを修正する

AppDelegate.swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    BookApiSetup(controller.binaryMessenger, MyApi())
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

追加したコードは以下の二行。

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
BookApiSetup(controller.binaryMessenger, MyApi())

実行する


tap! と書かれている部分をタップすると以下のように出力される。

もし動かなかったら、、、

生成したpigeon.h, pigen.mがうまく読み込めていない可能性があります。


これでやってみてください。

Discussion