25
15

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 5 years have passed since last update.

Android/iOSの既存アプリからFlutterを呼び出す(Add to App project)

Last updated at Posted at 2019-04-19

Flutter Meetup Tokyo #8の登壇内容をまとめました。

発表資料はこちらです。
ちなみに、登壇当時は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の基本的な仕組み

スクリーンショット 2019-04-20 0.10.03.png

一般の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が作成されます。

スクリーンショット 2019-04-20 0.10.49.png 生成されたモジュールには、隠しパッケージとして`.android`と`.ios`パッケージが生成されています。これがネイティブとDartとのブリッジとして動作します。

各OSの設定

次は先ほど作ったモジュールをAndroid/iOSから参照する設定を行います。

Androidプロジェクトの設定

CompileOptionでJava1.8の指定を行います。

build.gradle
android {
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}

次にFlutterモジュールへのpathを設定します。

setting.gradle
include ':app'
// add below
setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir.parentFile,
  'モジュール名/.android/include_flutter.groovy'))

最後におなじみのimplementationです。

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

[optional]AndroidX対応

既存のプロジェクトがAndroidXに対応している場合はもう一手間必要です。既存のテンプレートモジュールではAndroidXに対応していないため、手動でモジュール内の.androidをAndroidX対応させる必要があります。といっても、importを変えてあげるくらいなので、さほど手間ではないと思います。

iOSプロジェクトの設定

cocoapodsを使用するため、Podfileを編集します。(Podfileがない場合はpod initを実行してください)

Podfile
flutter_application_path = '../モジュール名/'
eval(File.read(
      File.join(flutter_application_path,
                'ios', 'Flutter', 'podhelper.rb')),
      binding)

Podfileを生成したらpod installを実行してください。
次にプロジェクト設定を行います。
Flutterは現状、bitcodeに対応していないため、Enable bitcodeNoにしてください。
スクリーンショット 2019-04-19 23.17.27.png

最後にbuild phaseにDartのビルドスクリプトを組み込みます。
スクリーンショット 2019-04-19 23.18.25.png
Build PhasesからNew Run Script Phaseを行い、下記のScriptを記述してください。

RunScript
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

上記スクリプトを記述したら、一番上のTarget Dependenciesのすぐ下にドラッグで移動させてください。
スクリーンショット 2019-04-19 23.23.03.png

各OSでの呼び出し

ここまでで、まずビルドは通るようになっているはずです。
ここからは既存のプロジェクトからの呼び出し方について説明します。

Androidからの呼び出し

AndroidからはFragmentとして呼び出す方法と、Viewとして呼び出す方法があります。が、どちらも使い方にあまり違いはありません。

Viewとして呼び出す

MainActivity.kt
val flutterView = Flutter.createView(
    this,      // Activity
    lifecycle, // Lifecycle
    "route1")

このコードを実行すると、Android画面内におなじみのFlutterの画面がViewとして表示されます。
スクリーンショット 2019-04-19 23.26.43.png
第三引数の"route1"については、後述します。

Fragmentとして呼び出す

MainActivity.kt
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.container,
                    Flutter.createFragment("route2"))
transaction.commit()

Viewの場合とほぼ同じであることがわかります。
ここで先ほども出てきた"route"について触れておきます。

What is "route"?

ネイティブからFlutterを呼び出した際、Dartでは以下のコードが実行されます。

main.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.swiftFlutterAppDelegateを継承させます。

AppDelegate.swift
import Flutter

@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
}

なお、FlutterAppDelegateは以下のような定義になっています。

FlutterAppDelegate.swift
@interface FlutterAppDelegate: UIResponder<UIApplicationDelegate, ...>

また、pluginを利用する場合はもう少し実装が必要になります。

AppDelegate.swift
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から呼ぶ

呼び方はシンプルです。

ViewController.swift
let flutterViewController = FlutterViewController()
flutterViewController.setInitialRoute("route3")
present(flutterViewController, animated: false, completion: nil)

基本的な使い方は以上です。

Tips

Flutterからネイティブ画面に戻る

main.dart
SystemNavigator.pop();

Hot Reload

App-to-App projectに対する、IDEとしてのHot Reloadサポートは、まだIn progressの状態です。が、コマンドラインツールは既に提供されています。

$ cd flutterモジュールのパス
$ flutter attach

コマンドを実行すると、以下のような画面になります。
スクリーンショット 2019-04-20 0.35.24.png
Hot Reloadにはpress "r"、Hot Restartにはpress "R"とあります。
また、http://127.0.0.1....とURLがひとつありますが、これをブラウザで開くとデバッグツールがみれます。
スクリーンショット 2019-04-20 0.37.45.png

Method Channel

今回の話とは逆のパターンで、こちらの方が利用シーンは圧倒的に多いと思います。Qiitaの記事を書いていますので、ご参考までにどうぞ。
FlutterでAndroid/iOSのネイティブ画面を表示する

公式ページはこちら
https://flutter.dev/docs/development/platform-integration/platform-channels

25
15
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
25
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?