はじめに
この記事はFlutter公式( https://flutter.dev/docs/development/add-to-app/multiple-flutters )に載っているMultiple Flutter screens or viewsを試してみた記事になります。
今までadd-to-appは
- 最初の画面を起動するのに時間がかかる(のでFlutterEngineのキャッシュが必要だった)
- 1画面だけ扱うことを想定している(複数の画面を扱うのは無理では無いがトリッキーな方法が必要だった)
- メモリを食う
などの課題がありました。
特に1画面しか扱うことを想定していないという制限は大きく、プロダクションでは実質使えない状態でした。
Flutter2.0のリリースに伴い実装されたFlutterEngineGroupを使うことでこれらを解決できます!
前提・環境
- Android Studio 4.2.1 (Android Studio CanaryはFlutterをサポートしていないので注意!)
- Flutter 2.2.1
- Flutter doctorを通していること
- 公式( https://flutter.dev/docs/development/add-to-app/android/project-setup )に従い、add-to-appを済ませていること
実装
FlutterEngineGroupの宣言
Applicationクラスを拡張してFlutterEngineGroupを宣言します
class App : Application() {
lateinit var engines: FlutterEngineGroup
override fun onCreate() {
super.onCreate()
engines = FlutterEngineGroup(this)
}
}
遷移処理の準備
EngineBindingsクラスを用意します
package com.example.addtoapptest
import android.app.Activity
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
interface EngineBindingsDelegate {
fun onNext()
}
class EngineBindings(activity: Activity, delegate: EngineBindingsDelegate, entrypoint: String) {
val channel: MethodChannel
val engine: FlutterEngine
val delegate: EngineBindingsDelegate
init {
val app = activity.applicationContext as App
// This has to be lazy to avoid creation before the FlutterEngineGroup.
val dartEntrypoint =
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), entrypoint
)
engine = app.engines.createAndRunEngine(activity, dartEntrypoint)
this.delegate = delegate
channel = MethodChannel(engine.dartExecutor.binaryMessenger, "multiple-flutters")
}
fun attach() {
channel.setMethodCallHandler { call, result ->
}
}
fun detach() {
// TODO: Uncomment after https://github.com/flutter/engine/pull/24644 is on stable.
// engine.destroy();
channel.setMethodCallHandler(null)
}
}
Flutter用のActivityの用意
FlutterActivityと上記で宣言したEngineBindingsを継承したActivityを作成します。
entrypoint = ""
が呼び出す画面の関数名になりますので、ここはdart側に合わせて適宜変更してください。
package com.example.addtoapptest
import android.content.Context
import android.content.Intent
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class SingleFlutterActivity : FlutterActivity(), EngineBindingsDelegate {
private val engineBindings: EngineBindings by lazy {
EngineBindings(activity = this, delegate = this, entrypoint = "firstScreen")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
engineBindings.attach()
}
override fun onDestroy() {
super.onDestroy()
engineBindings.detach()
}
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return engineBindings.engine
}
override fun onNext() {
val flutterIntent = Intent(this, MainActivity::class.java)
startActivity(flutterIntent)
}
}
dart側の準備
main.dartに@pragma('vm:entry-point')
で新しいエントリポイントを記述します。
ここで宣言した関数名とkotlin側で呼び出すエントリポイントを一致させましょう。
import 'package:flutter/material.dart';
// MyAppの内容は省略しています
@pragma('vm:entry-point')
void firstScreen() => runApp(MyApp());
void main() => runApp(MyApp());
以上です。AndroidManifestにActivityに登録するのを忘れずに!
補足
- FlutterEngineGroupはまだまだ実験的な機能とアナウンスされているので、実装はすぐ変わるかもしれません。
- 実験的な機能であるためか、FlutterEngineGroupクラスではプロダクションで使わないでくださいというコメントがあります。導入の際は自己責任で!( https://api.flutter.dev/javadoc/io/flutter/embedding/engine/FlutterEngineGroup.html )
-
@pragma('vm:entry_point')
でも特にエラーが出ない上、なぜかデバッグビルドだと動いてリリースビルドだと動かないという不思議な動作をしました。アンダーバーではなくハイフンなのでご注意を!