前面几篇記事では、Navigator コンポーネント、Flex レイアウト、画像の読み込み、Widget ライフサイクルなど Flutter 開発の基礎知識について紹介しました。記事のリンクは以下の通りです:
- Flutter シリーズ之 Navigator コンポーネント使用
- Flutter シリーズ之 Flex レイアウト詳解
- Flutter シリーズ之画像読み込み詳解
- Flutter シリーズ之 Widget ライフサイクル
今日は Flutter 混合開発モードと、Android 既存プロジェクトに Flutter モジュールを追加する方法について紹介します。主な内容は以下の通りです:
- Flutter 混合開発モード
- Flutter Module の作成方法
- Flutter の追加方法
- 単一の Flutter ページの追加
- FlutterFragment の追加
- Flutter と Android の相互遷移
Flutter 混合開発モード#
Flutter 混合開発には一般的に二つの方法があります:
- ネイティブプロジェクトを Flutter プロジェクトのサブプロジェクトとして直接使用する方法。Flutter はデフォルトで android と ios のプロジェクトディレクトリを作成します。
- Flutter Module を作成し、既存のネイティブプロジェクトに依存関係として追加する方法。
第二の方法は第一の方法に比べてより疎結合であり、特に既存プロジェクトの改造コストが小さくなります。
Flutter Module の作成方法#
Flutter Module を作成するには二つの方法があります:
- コマンドを使用して Flutter Module を作成する
flutter create -t module --org com.manu.flutter flutter_module_one
- Android Studio (AS) を使用して Flutter Module を作成する
AS で File->New->New Flutter Project を選択し、Flutter Module を選択して Flutter Module サブプロジェクトを作成します。
Flutter の追加方法#
ここでの追加方法は第二の方法、すなわち Flutter Module の形で Flutter モジュールを既存プロジェクトに追加することを指します。Android 既存プロジェクトに Flutter を追加する方法は二つあります:
- aar 形式で Android 既存プロジェクトに統合する方法:
Flutter Module を作成した後、aar 形式にコンパイルする必要があります。以下のコマンドで aar のコンパイルを行うことができます:
// Flutter Module のルートディレクトリに移動
cd flutter_module
flutter build aar
Android でも AS ツールを使用して aar をコンパイルできます。Build->Flutter->Build AAR を選択して aar のコンパイルを行います。
その後、主プロジェクトの build.gradle ファイルで関連設定を行います。以下を参考にしてください:
repositories {
maven {
url 'G:/xxx/flutter_module_one/build/host/outputs/repo'
}
maven {
url 'https://storage.googleapis.com/download.flutter.io'
}
}
buildTypes {
profile {
initWith debug
}
}
dependencies {
debugImplementation 'com.manu.flutter.flutter_module_one:flutter_debug:1.0'
profileImplementation 'com.manu.flutter.flutter_module_one:flutter_profile:1.0'
releaseImplementation 'com.manu.flutter.flutter_module_one:flutter_release:1.0'
}
- Flutter module の形で既存の Android プロジェクトに統合する方法:
settings.gradle ファイルに Flutter module を以下のように設定します:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir,
'flutter_module_two/.android/include_flutter.groovy'
))
次に、build.gradle ファイルに Flutter module の依存関係を追加します:
dependencies {
implementation project(':flutter')
}
単一の Flutter ページの追加#
Activity を作成し、FlutterActivity を継承し、AndroidManifest.xml ファイルに宣言します:
<activity
android:name=".AgentActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
この FlutterActivity をどのように起動するかは以下の通りです:
// デフォルトルート /
myButton.setOnClickListener {
startActivity(
FlutterActivity.createDefaultIntent(this)
)
}
// カスタムルート
myButton.setOnClickListener {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(this)
)
}
上記のコードは内部で独自の FlutterEngine インスタンスを作成します。各 FlutterActivity は独自の FlutterEngine を作成するため、標準の FlutterActivity を起動すると、画面が表示されるまでに短い遅延が発生します。事前キャッシュされた FlutterEngine を使用することでその遅延を減少させることができます。実際には、内部で事前キャッシュされた FlutterEngine が存在するかどうかを確認し、存在する場合はその FlutterEngine を使用し、そうでない場合は非事前キャッシュの FlutterEngine を使用します。ソースコードの判断は以下の通りです:
/* package */ void setupFlutterEngine() {
Log.v(TAG, "Setting up FlutterEngine.");
// 1. 事前キャッシュされたFlutterEngineを確認
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException(
"The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
+ cachedEngineId
+ "'");
}
return;
}
// 2. カスタムのFlutterEngineがあるかどうか
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
Log.v(
TAG,
"No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this FlutterFragment.");
// 3. 新しいFlutterEngineを作成
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false);
isFlutterEngineFromHost = false;
}
事前キャッシュされた FlutterEngine の使用方法については、公式サイトを参照してください。
FlutterFragment の追加#
同様に、Android 既存プロジェクトに FlutterFragment を追加するために、後の通信を便利にするために FlutterFragment を継承したカスタム Fragment を作成し、それをある Activity に追加します。以下の通りです:
class AgentActivity2 : FragmentActivity() {
private val flutterFragmentTag = "flutter_fragment_tag"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_agent2)
val fragmentManager = supportFragmentManager
var flutterFragment = fragmentManager.findFragmentByTag(flutterFragmentTag)
if (flutterFragment == null) {
flutterFragment = MFlutterFragment
.withNewEngine()
?.build()
if (flutterFragment != null) {
fragmentManager.beginTransaction()
.add(R.id.ff_container, flutterFragment, flutterFragmentTag)
.commit()
}
}
}
}
FlutterFragment を追加する Activity への遷移は、以下のように Intent を使用します:
// FlutterFragmentを追加するActivityへの遷移
val intent = Intent(this@LaunchActivity, AgentActivity2::class.java)
startActivity(intent)
Flutter と Android の相互遷移#
Flutter と Android の相互遷移について、上記では基本的にネイティブ Android から FlutterActivity への遷移や FlutterFragment を追加する Activity について説明しましたが、Flutter ページからネイティブ Activity へはどのように遷移するのでしょうか。
Flutter とネイティブの通信メカニズムには、主に MethodChannel、EventChannel、BasicMessageChannel が含まれます。この内容は多岐にわたるため、一小節では紹介しきれませんが、ここでは MethodChannel の使用について簡単に紹介します。MethodChannel は主にメソッド呼び出しを伝達するために使用され、Flutter ページから Android のネイティブ API が提供するメソッドを呼び出すことができます。
Flutter からネイティブ Android への遷移を実現するために MethodChannel を使用する方法について説明します。単一の Flutter ページであっても FlutterFragment を追加している場合でも、それぞれ FlutterActivity と FlutterFragment を継承し、configureFlutterEngine メソッドをオーバーライドする必要があります。以下を参考にしてください:
// FlutterActivity
class AgentActivity : FlutterActivity() {
private val tag = AgentActivity::class.java.simpleName;
private val channel = "com.manu.startMainActivity"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.d(tag, "configureFlutterEngine")
// MethodChannelを登録し、Flutterページのメソッド呼び出しをリスンします
MethodChannel(flutterEngine.dartExecutor, channel)
.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
if ("startMainActivity" == methodCall.method) {
MainActivity.startMainActivity(this)
result.success("success")
} else {
result.notImplemented()
}
}
}
companion object {
/**
* NewEngineIntentBuilderを再作成することで効果を保証します
*/
fun withNewEngine(): MNewEngineIntentBuilder? {
return MNewEngineIntentBuilder(AgentActivity::class.java)
}
}
/**
* カスタムNewEngineIntentBuilder
*/
class MNewEngineIntentBuilder(activityClass: Class<out FlutterActivity?>?) :
NewEngineIntentBuilder(activityClass!!)
}
// FlutterFragment も同様
// 省略 ...
必ず withNewEngine メソッドをオーバーライドしてください。そうしないと、Flutter からネイティブ Activity への遷移が失敗します。Flutter ページから invokeMethod を使用してメソッド呼び出しを行います。具体的には以下の通りです:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// このウィジェットはアプリケーションのルートです。
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Flutter ページ"),
centerTitle: true,
),
body: PageWidget()
),
routes: <String, WidgetBuilder>{
},
);
}
}
/// Stateful Widget
class PageWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _PageState();
}
}
/// State
class _PageState extends State<PageWidget> {
MethodChannel platform;
@override
void initState() {
super.initState();
platform = new MethodChannel('com.manu.startMainActivity');
}
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_startMainActivity();
},
child: Text("Flutter から Android へ"),
);
}
/// ネイティブActivityへ遷移
void _startMainActivity() {
platform.invokeMethod('startMainActivity').then((value) {
print("value:startMainActivity");
}).catchError((e) {
print(e.message);
});
}
}
また、Flutter とネイティブの通信メカニズムについては、今後の記事で紹介する予定です。