前面几篇文章紹介了 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 ページの追加#
FlutterActivity を継承する Activity を作成し、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 を委譲します。
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 は主にメソッド呼び出しを伝達するために使用され、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 とネイティブの通信メカニズムについては、今後の文章で紹介する予定です。