この記事はMakuake Advent Calendar 2021の6日目の記事です。
概要
Flutter-Native間の通信は受け渡しできるデータ型がStringやprimitive型に限られたり、ネイティブメソッドを呼び出す際にメソッド名を文字列で取り扱う場面があるなど保守性に多少不安を感じてしまうところがある。
Pigeonを使うと、事前に定義したschemeをもとにFlutter/Java/Objective-Cのコードを自動生成しFlutter-ネイティブ間の通信をより安全に実装することができる。
例えば既存アプリにFlutterを部分的に導入したいがどうしてもFlutter-ネイティブのやりとりが発生してしまうケースにおいて、Pigeonを活用することでその障壁を下げられるかもしれない。
今回はFlutter化したログイン画面でのログイン成功時にネイティブ側にコールバックするケースを想定して実装してみる。
[ネイティブ画面] -> [ログイン画面(Flutter)] -(SUCCESS!!)-> [ネイティブ画面]
Add Flutter to existing app
https://docs.flutter.dev/development/add-to-app/android/project-setup
https://docs.flutter.dev/development/add-to-app/ios/project-setup
こちらに紹介されている手順でflutter moduleをAndroid/iOSプロジェクトに組み込んでいく。
Flutter Moduleの作成
以下のコマンドを実行するとflutter_module
というディレクトリ名でflutter moduleが生成される。
$ flutter create --template module flutter_module
そして今回は以下のようなログイン画面を作成した。
AndroidアプリにFlutter Moduleを組み込む
AndroidアプリにFlutter Moduleを組み込む方法は2種類あり、今回はOption B の手順で組み込んでいく。
flutter_module
と同じ階層にProjectが生成されるようにAndroid Studioで新規Project(ここではandroid_app
というプロジェクト名)を作成して、以下のようなディレクトリ構成にする。
path/to/working_dir/
|-- flutter_module
|-- android_app
settings.gradle
に以下の記述を追加してflutter moduleをgradleで認識できるようにする。
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
app/build.gradle
に以下の記述を追加してflutter_moduleをアプリに組み込む。
dependencies {
...
implementation project(':flutter')
...
}
ここでアプリを問題なくbuildできればflutter_moduleの組み込みは成功。
以下のようなMainActivityを作成してログインボタンを押すと先述のFlutter画面を起動できるようにしておく。
// Flutter Moduleのmain関数が実行されLogin画面が表示される
class FlutterLoginActivity : FlutterActivity() {
}
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
findViewById<Button>(R.id.move_to_login_button)
.setOnClickListener {
val intent = Intent(this, FlutterLoginActivity::class.java)
startActivity(intent)
}
}
}
iOSアプリにFluter Moduleを組み込む
// TODO: 書く
Pigeonのセットアップ
https://pub.dev/packages/pigeon
flutter_module
配下で以下のコマンドを実行してPigeonの依存関係を追加する。
$ dart pub add pigeon
ログイン成功のコールバックを実装
ここからは実際にPigeonを使ったFlutter-ネイティブ間の通信部分を実装する。
大まかな流れは以下の通り。
-
flutter_module
でschemeを定義 - schemeをもとにしたFlutter/Android/iOS側のコードを自動生成
-
flutter_module
の適当なところでschemeで定義したコールバックメソッドを実行する - Android/iOS側でschemeで自動生成されたInterfaceの実装クラスを作成する
- Flutter側と4で作成した実装クラスをsetupする
Schemeファイルの作成
flutter_module
配下に pigeons/login_scheme.dart
ファイルを追加。以下のようにcallbackを定義する。
import 'package:pigeon/pigeon.dart';
@HostApi()
abstract class HostLoginCallback {
void onSuccess();
}
flutter_module
配下に run_pigeon.sh
ファイルを追加する。
#!/bin/sh
flutter pub run pigeon --input pigeons/login_scheme.dart \
--dart_out lib/login_scheme.dart \
--java_out ../android_app/app/src/main/java/your/package/name/LoginScheme.java \
--java_package "your.package.name
# TODO: iOS用のoptions
$ ./run_pigeon.sh
を実行すると logins_scheme.dart
の定義を元にdart, Java, (iOS)コードが自動生成される。
今回は flutter_module/lib
に login_scheme.dart
、 android_app/app/src/...
に LoginScheme.java
が生成される。
Flutter App
以下のようにログインボタン押下時にコールバックメソッドを呼び出す。
class _LoginPageState extends State<LoginPage> {
...
final HostLoginCallback loginCallback = HostLoginCallback();
@override
Widget build(BuildContext context) {
...
TextButton(
...
onPressed: () {
loginCallback.onSuccess();
},
child: const Text(
'ログイン',
)
...
)
}
}
Android App
Android側では自動生成された HostLoginCallback
の実装クラスを定義し、flutter側のcallbackとの紐付けを行う。今回は以下のような形で FlutterLoginActivity
に実装してみた。
class FlutterLoginActivity : FlutterActivity() {
// 自動生成されたInterfaceの実装クラス
inner class HostLoginCallbackHandler: LoginScheme.HostLoginCallback {
override fun onSuccess() {
Toast.makeText(this@FlutterLoginActivity, "SUCCESS!!", Toast.LENGTH_SHORT).show()
finish()
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Flutter側のcallbackとネイティブ側の実装クラスを紐付け
LoginScheme.HostLoginCallback.setup(flutterEngine.dartExecutor, HostLoginCallbackHandler())
}
}
これでFlutter側で HostLoginCallback#onSuccess()
が呼び出された時 HostLoginCallbackHandler#onSuccess()
が実行され、以下のような画面遷移となる。
iOS App
// TODO: 書く
最後に
今回紹介した run_pigeon.sh
だと自動生成コードがVCSで管理される領域に吐き出される。app/build
あたりに吐き出す形を検討したい。
また、自動生成コマンドを漏れなく実行できるようにbuildタスクをhookしてコマンドを走らせる形にできると良さそう。
参考
https://docs.flutter.dev/development/platform-integration/platform-channels
https://github.com/flutter/samples/tree/master/add_to_app/books