Flutter Meetup Tokyo #8の登壇内容をまとめました。
この後のLT資料です。Add2Appについてお話します。https://t.co/ybc9EPbKoG
— m.kosuke (@kosuke_mtm) 2019年3月26日
#flutter_meetup_tokyo
発表資料はこちらです。
ちなみに、登壇当時はAdd2App
という表記でしたが、現在はAdd-to-App
となっていますので、本記事ではApp-to-App
で統一します。
サンプルコードもこちらにおいておきました。
https://github.com/shcahill/Add2AppSample
App-to-App projectとは?
公式ページはこちらです。
これによると、
- 既存のAndroid/iOSのプロジェクトから簡単にFlutterを呼び出すことができる
- 現状、In previewの機能であり、master channelからしか利用できない(2019/3/17時点)
となっています。
Flutterの利用シーンが現状、新規アプリ作成時に限定されているため、開発者がFlutterに触れる機会も同時に限定されています。その対策としてAdd-to-App projectが進められています。React Nativeでは既に存在する機能でもあり、今後Flutterが普及するかどうかのひとつの指標になるかもしれません。
現在の進捗状況に関しては、こちらから確認できます。
https://github.com/flutter/flutter/projects/28
また、ひとつ注意点があります。master channelでしか利用できないと述べましたが、その場合安定していないFlutterバージョンを使用することになります。当然、不具合も混入している可能性も高くなります。ですので、最低限既知の不具合を下記のBad Buildsから確認の上、利用することをおすすめします。
https://github.com/flutter/flutter/wiki/Bad-Builds
Add-to-Appの基本的な仕組み
一般のFlutterプロジェクトでは、Dartコードのあるlib
パッケージと同じ階層にandroid
パッケージとios
パッケージが同居しています。
一方App-to-Appでは、Android/iOSプロジェクトと同列の階層に、Flutterモジュールを用意して使います。Androidからはgradle経由でモジュールとして取り込み、iOSではcocoapodsを使って参照します。
実装方法
Flutter moduleの作成
master channelに切り替えます。
※channelを切り替えたあと、他のプロジェクトをビルドする際は元に戻すことを忘れないようにしておきましょう
$ flutter channel master
次にFlutterモジュールを生成します
$ flutter create -t module モジュール名
これでFlutter module project templateが作成されます。
生成されたモジュールには、隠しパッケージとして`.android`と`.ios`パッケージが生成されています。これがネイティブとDartとのブリッジとして動作します。各OSの設定
次は先ほど作ったモジュールをAndroid/iOSから参照する設定を行います。
Androidプロジェクトの設定
CompileOptionでJava1.8の指定を行います。
android {
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
次にFlutterモジュールへのpathを設定します。
include ':app'
// add below
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'モジュール名/.android/include_flutter.groovy'))
最後におなじみのimplementationです。
dependencies {
implementation project(':flutter')
...
}
[optional]AndroidX対応
既存のプロジェクトがAndroidXに対応している場合はもう一手間必要です。既存のテンプレートモジュールではAndroidXに対応していないため、手動でモジュール内の.android
をAndroidX対応させる必要があります。といっても、importを変えてあげるくらいなので、さほど手間ではないと思います。
iOSプロジェクトの設定
cocoapodsを使用するため、Podfileを編集します。(Podfileがない場合はpod init
を実行してください)
flutter_application_path = '../モジュール名/'
eval(File.read(
File.join(flutter_application_path,
'ios', 'Flutter', 'podhelper.rb')),
binding)
Podfileを生成したらpod install
を実行してください。
次にプロジェクト設定を行います。
Flutterは現状、bitcodeに対応していないため、Enable bitcode
をNo
にしてください。
最後にbuild phaseにDartのビルドスクリプトを組み込みます。
Build Phases
からNew Run Script Phase
を行い、下記のScriptを記述してください。
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
上記スクリプトを記述したら、一番上のTarget Dependencies
のすぐ下にドラッグで移動させてください。
各OSでの呼び出し
ここまでで、まずビルドは通るようになっているはずです。
ここからは既存のプロジェクトからの呼び出し方について説明します。
Androidからの呼び出し
AndroidからはFragmentとして呼び出す方法と、Viewとして呼び出す方法があります。が、どちらも使い方にあまり違いはありません。
Viewとして呼び出す
val flutterView = Flutter.createView(
this, // Activity
lifecycle, // Lifecycle
"route1")
このコードを実行すると、Android画面内におなじみのFlutterの画面がViewとして表示されます。
第三引数の"route1"
については、後述します。
Fragmentとして呼び出す
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.container,
Flutter.createFragment("route2"))
transaction.commit()
Viewの場合とほぼ同じであることがわかります。
ここで先ほども出てきた"route"
について触れておきます。
What is "route"?
ネイティブからFlutterを呼び出した際、Dartでは以下のコードが実行されます。
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'route1':
return SomeWidget();
case 'route2':
return SomeWidget();
default:
return Center(child: Text('Unknown route: $route'));
}
}
このようにDart側では、ネイティブ側から引数で渡した文字列が受け取ることができ、その文字列を元に表示するWidgetを振り分けることができるようになっています。
iOSからの呼び出し
Androidと異なり、少々前準備が必要です。
FlutterAppDelegateを継承
まずAppDelegate.swift
をFlutterAppDelegate
を継承させます。
import Flutter
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
}
なお、FlutterAppDelegate
は以下のような定義になっています。
@interface FlutterAppDelegate: UIResponder<UIApplicationDelegate, ...>
また、pluginを利用する場合はもう少し実装が必要になります。
import Flutter
import FlutterPluginRegistrant
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
var flutterEngine: FlutterEngine?
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.flutterEngine = FlutterEngine(name: "io.flutter", project: nil)
self.flutterEngine?.run(withEntrypoint: nil)
GeneratedPluginRegistrant.register(with: self.flutterEngine)
return super.application(application, didFinishLaunchingWithOptions: launchOptions); |
}
}
ViewControllerから呼ぶ
呼び方はシンプルです。
let flutterViewController = FlutterViewController()
flutterViewController.setInitialRoute("route3")
present(flutterViewController, animated: false, completion: nil)
基本的な使い方は以上です。
Tips
Flutterからネイティブ画面に戻る
SystemNavigator.pop();
Hot Reload
App-to-App projectに対する、IDEとしてのHot Reloadサポートは、まだIn progressの状態です。が、コマンドラインツールは既に提供されています。
$ cd flutterモジュールのパス
$ flutter attach
コマンドを実行すると、以下のような画面になります。
Hot Reloadにはpress "r"
、Hot Restartにはpress "R"
とあります。
また、http://127.0.0.1....
とURLがひとつありますが、これをブラウザで開くとデバッグツールがみれます。
Method Channel
今回の話とは逆のパターンで、こちらの方が利用シーンは圧倒的に多いと思います。Qiitaの記事を書いていますので、ご参考までにどうぞ。
FlutterでAndroid/iOSのネイティブ画面を表示する
公式ページはこちら
https://flutter.dev/docs/development/platform-integration/platform-channels