FlutterでPigeonを使ったカスタムプラグインの作成方法
このチュートリアルでは、Pigeonパッケージを使用して、AndroidとiOSプラットフォームをターゲットとするFlutterプラグインを作成します。
FlutterはUIフレームワークなので、ネイティブプラットフォームと通信することはいつも必要とされるものではありませんし、必要があったとしても、すでに特定のユースケースでこれを行うたくさんのパッケージがあります。
しかし、ネイティブプラットフォームが提供する機能がpub.devで公開されているパッケージにまだない場合もあります。そのような場合、私たちの出番です!Flutter側でDartコードを書いてプラットフォームを呼び出し、アプリがサポートしているそれぞれのプラットフォームで必要な機能を呼び出すネイティブプラットフォームコードを書く必要があります!
プラットフォームチャネル
Flutterでネイティブプラットフォームと通信する方法の1つは、MethodChannel
を使用することです。以前の記事では、Flutterでメソッドチャネルを使用してプラグインを構築する方法やネイティブプラットフォームコードを呼び出す方法について説明しました。
MethodChannel
の使用は比較的簡単ですが、実際はかなり時間がかかることがあります。MethodChannel
を介してメソッドを呼び出す際、int
、double
、bool
、String
などの単純な型や、そのような型を値とするマップやリストだけを引数として渡すことができます。対応しているデータ型の完全なリストはこちらで見ることができます。複雑なオブジェクトを引数として渡す必要がある場合は、これを例えばキーが文字列で値がキーのマップに解析するロジックが必要になります。また、ネイティブプラットフォームから返されるデータに対しても同様のことをする必要があります。
json_serializable
のようなパッケージを使用して、データをJSONから解析したり、逆にすることで解析ロジックを導入することを回避し、自分たちの時間を節約することができます。ただし、ネイティブプラットフォームが期待する通りの形式でデータを返していなければ、およびその逆も同様の場合は、解析が失敗します。
Pigeon
Pigeonは、Flutterとホストプラットフォーム間の通信に必要なすべてのコードを生成するコードジェネレータパッケージです。あなたがすることはAPIを定義するだけです。これは便利です、なぜなら解析ロジックについて心配する必要がなく、通信が型安全であることが保証されるからです。
2022年7月10日現在、PigeonはAndroidとiOSのみをサポートしており、それぞれJavaとObjective-Cのコードを生成します(Swiftは実験的)。生成されたコードはKotlinやSwiftからもアクセスできます。また、C++を使用した実験的なWindowsサポートもあります。
この投稿では、Pigeonを使用してシンプルなプラグインを作成します。結果としてできるものは、私たちが以前にここで構築した(フェイクの)アプリ使用状況プラグインと同じものですので、結果を比較できます。
プラグインはシンプルで、すべてのアプリとその使用状況をリストで返し、特定のアプリに時間制限を設定する機能をサポートするものです。このチュートリアルの範囲外であるため、ネイティブ側でこの機能を実際に実装せず、代わりにネイティブ側からダミーデータを返します。
プラグインテンプレート
始めるにあたって、flutter create
を使用してプラグインテンプレートを使ってFlutterプラグインを作成します。
flutter create --org dev.dartling --template=plugin --platforms=android,ios app_usage_pigeon
これにより、このプラグインを使用するプロジェクトの例としてプラグインのコードが生成されます。デフォルトでは、生成されたAndroidコードはKotlinで、iOSはSwiftで書かれていますが、それぞれ -a
や -i
フラグを使用してJavaまたはObjective-Cを指定することができます(-a java
および/または -i objc
)。
プラグインテンプレートにはかなりの量のコードが含まれています。このチュートリアルの文脈では、次のことを知っていれば十分です:
Dart側には、以下の3つのクラスがあります:
AppUsagePlatform
- プラグインの「インターフェース」/APIであり、PlatformInterface
を実装します。MethodChannelAppUsage
- メソッドチャネルを使用したAppUsagePlatform
の実装です。AppUsage
- プラグインを使用する必要があるアプリでメソッドを公開するクラスです。
生成されたKotlinコードはAppUsagePlugin.kt
で、メソッドチャネルを使用しています。これはpubspec.yaml
でAndroidプラットフォームのプラグインクラスとして定義されているため、後でいくつかの変更を加えることになりますが、まだ必要です。同じことがSwiftAppUsagePlugin.swift
およびAppUsagePlugin.h
とAppUsagePlugin.m
を含むSwiftコードにも当てはまります。
このチュートリアルでは、メソッドチャネルではなくPigeonを使用してAppUsagePlatform
の実装を書きます。MethodChannelAppUsage
は必要ありませんので削除します。
注:PlatformInterface
を使用する新しいプラグインテンプレートでは、アプリで単にネイティブコードを呼び出したいだけであれば本当に必要ない可能性のあるかなりの量のコードを導入します。プラグインや別パッケージを作成せずにPigeonを使用することも可能ですが、このチュートリアルではそのように進めます。
pigeon
パッケージのインストール
パッケージをインストールしましょう:
flutter pub add --dev pigeon
または、pubspec.yaml
にこれを追加します:
dev_dependencies:
pigeon: ^3.2.3
App Usage APIの定義
Pigeonの動作方法はとてもシンプルです;APIはlib
フォルダの外にDartクラスとして定義します(Pigeonは開発依存関係です)。APIクラスは@HostApi()
デコレータを持つ抽象クラスであるべきで、そのメソッドは@async
デコレータを持つべきです。
pigeons
という新しいディレクトリ内でApp Usage APIを定義しましょう:
// pigeons/app_usage_api.dart
import 'package:pigeon/pigeon.dart';
enum State { success, error }
class StateResult {
final State state;
final String message;
StateResult(this.state, this.message);
}
class UsedApp {
final String id;
final String name;
final int minutesUsed;
UsedApp(this.id, this.name, this.minutesUsed);
}
@HostApi()
abstract class AppUsageApi {
@async
String? getPlatformVersion();
@async
List<UsedApp> getApps();
@async
StateResult setAppTimeLimit(String appId, int minutesUsed);
}
警告と制約
APIの定義は比較的単純でしたが、言及するべきいくつかの点があります:
Futures
戻り値をFuture
として指定する必要はありませんが、生成されたコードにおいてはそれになります。つまりgetPlatformVersion
は実際には生成されたDartコードでFuture<String?>
を返します。
インポートは許可されません
package:pigeon/pigeon.dart
以外のインポートは許可されていません。これは、すべてのモデルクラスはこのPigeon APIファイル内で定義されるべきであるということです。
サポートされるデータ型
前に述べたように、単純なJSONのような値のみがサポートされます。これは、DateTime
やDuration
のような有用なDart型を使用することができないことを意味します。これにより、アプリ内で使用するモデルにPigeonモデルを変換するための追加のマッピングロジックがまだ必要になるかもしれません。UsedApp
のminutesUsed
の場合、分からDuration
を手作業で作成する必要がありますが、最初からこれがDuration
であればよかったと思います。
基本的な戻り値の型に対するenumはまだサポートされていません
メソッドからenumを返すことはできませんが、メソッドのパラメータとしてenumを持つことはできます。別のクラスでenumをラップしている限り、enumを返すことは可能です。
// 無効です。enumはリターンされることはできません。
enum ResultState { success, error }
@HostApi()
abstract class AppUsageApi {
@async
ResultState getState();
}
// 有効です。enumはメソッドのパラメータと戻り値のオブジェクトのフィールドであれば使用できます。
enum ResultState { success, error }
class ApiResult {
final ResultState state;
final String message;
ApiResult(this.state, this.message);
}
@HostApi()
abstract class AppUsageApi {
@async
ApiResult getResult();
@async
void setState(ResultState state);
}
ジェネリックはサポートされていますが、null許容型でのみ使用できます
HostApi
の定義で非nullとして定義しても、生成されたDartクラスでは代わりにList<Something?>
となります。
コードの生成
ジェネレータを実行する
APIを定義した後、flutter pub run pigeon
を使用してコードを生成できます。このコマンドにはかなりの引数が必要です。
flutter pub run pigeon \
--input pigeons/app_usage_api.dart \
--dart_out lib/app_usage_api.dart \
--java_package "dev.dartling.app_usage" \
--java_out android/src/main/java/dev/dartling/app_usage/AppUsage.java \
--experimental_swift_out ios/Classes/AppUsage.swift
将来的に実行しやすくするために、これをpigeon.sh
ファイルに保存します。
今はSwiftを実験的なサポートのあるものとして選択していますが、Objective-Cの場合は、experimental_swift_out
引数を次の3つの引数に置き換えるだけです。
--objc_header_out ios/Classes/AppUsageApi.h \
--objc_source_out ios/Classes/AppUsageApi.m \
--objc_prefix FLT
Dart
input
引数はAPIを定義したファイルであり、dart_out
はアプリで実際に使用するコードであるため、lib
フォルダ内である必要があります。
Java
java_package
は完全なパッケージ名であり、この場合はdev.dartling.app_usage
であり、java_out
は生成されるJavaファイルのパスです。
注意:生成されたJavaクラス名がPigeonのHostApi
と同じでないようにしてください。この場合、生成されるJavaクラスはAppUsage
であり、PigeonダートクラスのHostApi
名に定義されたAppUsageApi
インターフェースを含む公開AppUsageApi
インターフェースを含みます。同じ名前を使用した場合(初めに私がしたように!)、名前の重複が原因でコンパイルが失敗します。
注意:プラグインテンプレートがこの場合のようにKotlinを使用している場合、src/main
の下にあるjava/dev/dartling/app_usage
ディレクトリを手動で作成する必要があります。プラグインテンプレートの一部として生成されるのはkotlin/dev/dartling/app_usage
だけだからです。
Swift
experimental_swift_out
は生成されるSwiftファイルのパスです。
Objective-C
objc_header_out
とobjc_source_out
の引数はObjective-C側で生成されるファイルを決定し、objc_prefix
はオプションで生成されるクラス名のプレフィックスを決定します。
生成されたコードを理解する
flutter pub run pigeon
を実行した後にPigeonによって生成されるコードは、私たちが頻繁に見る必要があるものではありません。私たちが知っておく必要があることは、Javaクラスには私たちの実装クラスが実装すべきAppUsageApi
インターフェースがあるということです;これはJavaまたはKotlinのいずれかで行うことができます。Objective-Cの場合、FLT
プレフィックス(ジェネレータを実行する際の引数)に
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/yallurium/how-to-create-a-custom-plugin-in-flutter-with-pigeon-24nh