5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pigeonで安全なFlutter画面-ネイティブ画面のコミュニケーションを実装する

Posted at

この記事は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で認識できるようにする。

settings.gradle
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        'flutter_module/.android/include_flutter.groovy'
))

app/build.gradle に以下の記述を追加してflutter_moduleをアプリに組み込む。

app/build.gradle
dependencies {
    ...
    implementation project(':flutter')

    ...
}

ここでアプリを問題なくbuildできればflutter_moduleの組み込みは成功。

以下のようなMainActivityを作成してログインボタンを押すと先述のFlutter画面を起動できるようにしておく。

->

FlutterLoginActivity.kt
// Flutter Moduleのmain関数が実行されLogin画面が表示される
class FlutterLoginActivity : FlutterActivity() {

}
MainActivity.kt
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-ネイティブ間の通信部分を実装する。

大まかな流れは以下の通り。

  1. flutter_module でschemeを定義
  2. schemeをもとにしたFlutter/Android/iOS側のコードを自動生成
  3. flutter_module の適当なところでschemeで定義したコールバックメソッドを実行する
  4. Android/iOS側でschemeで自動生成されたInterfaceの実装クラスを作成する
  5. Flutter側と4で作成した実装クラスをsetupする

Schemeファイルの作成

flutter_module 配下に pigeons/login_scheme.dart ファイルを追加。以下のようにcallbackを定義する。

pigeons/login_scheme.dart
import 'package:pigeon/pigeon.dart';

@HostApi()
abstract class HostLoginCallback {
  void onSuccess();
}

flutter_module 配下に run_pigeon.sh ファイルを追加する。

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/liblogin_scheme.dartandroid_app/app/src/...LoginScheme.java が生成される。

Flutter App

以下のようにログインボタン押下時にコールバックメソッドを呼び出す。

main.dart
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 に実装してみた。

FlutterLoginActivity.kt
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

5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?