はじめに
お仕事でFlutterで作成した画面を既存のiOSアプリ・Androidアプリに追加する事になりました。
個人的にAndroidとFlutterは多少知識があるのですがiOS(特にObjective-C)の知識が皆無で少し苦戦したので、ネイティブアプリにFlutterモジュールを組み込む手順とハマったときの対応を備忘録的にまとめておきます。
既存iOSアプリはObjective-C、AndroidアプリはJavaで実装されているのでこの記事もそれに合わせたものとなっています。
また、iOSアプリではCocoaPodsを使って組み込み、Androidではsettings.gradle
でFlutterモジュールを追加させています。
基本的には以下の公式ドキュメントを参考にしました。
Add Flutter to existing app | Flutter
環境
- MacBookPro M1 BigSur 11.6
- Xcode:12.5.1
- AndroidStudio:2021.1
- FlutterPlugin:63.2.2
- DartPlugin:211.6693.111
% flutter --version
Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 77d935af4d (7 weeks ago) • 2021-12-16 08:37:33 -0800
Engine • revision 890a5fca2e
Tools • Dart 2.15.1
サンプルコード
以下にiOS、Android、Flutterモジュールのサンプルコードを置いておきます。
ディレクトリ構成
既存のiOSアプリ、AndroidアプリとFlutterモジュールのプロジェクトのディレクトリ構成は以下の通りです。
─ flutter_add-to-app_sample
├── AndroidSample
├── IOSApplicationSample
└── flutter_module_sample
Flutterモジュールの作成
まずは新規に追加したいFlutterモジュールを作成します。
今回はAndroid Studioのウィザードで作成しました。
Android Studioを使用しない場合は公式の手順を参考にしてください。
Androidアプリへの追加
次に既存のAndroidアプリからFlutterモジュールを呼び出してみます
AndroidアプリにFlutterモジュールを追加する
1. 既存Androidアプリのsettings.gradle
を修正する
settings.gradle
に以下のコードを追加します。
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module_sample/.android/include_flutter.groovy'
))
flutter_module_sample/.android/include_flutter.groovy
のコードを読み込む処理です。
こちらはFlutterモジュールのflutter pub get
した際に自動で生成されます。
2. 既存Androidアプリのapp/build.gradle
を修正する
dependencies
に以下の行を追加します。
implementation project(':flutter')
AndroidアプリからFlutterモジュールを呼ぶ処理を実装する
1.ActivityManifest.xml
を修正する
ActivityManifest.xml
に以下を追加してFlutterActivity
を起動できるようにする。
android:theme
には既存のものとか好きなものを入れてください。
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
追加した例は以下の通りです。
今回はandroid:theme
にアクションバーなしのテーマを適用しています。
2. 既存のソースコードからFlutterActivityを呼び出す
既存のソースコードにFlutter画面を起動する処理を追加します。
今回は単純にボタンが押下された際にFlutterActivity
を表示させています。
-
対象のActivityやFragmentでFlutterライブラリをインポート
import io,flutter.embedding.android.FlutterActivity;
-
Flutterの画面を開きたいアクションに以下の処理を実装
@Override
public void onClick(View view) {
startActivity(FlutterActivity.createDefaultIntent(view.getContext()));
}
実行
あとはAndroid Studioから既存アプリを起動してみます。
既存のAndroidネイティブアプリからFlutterの画面を起動し、正常に動作することを確認できました。
- もしビルド時に以下のエラーが出る場合
Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin class 'FlutterPlugin'.
Caused by: org.gradle.api.InvalidUserCodeException: Build was configured to prefer settings repositories over project repositories but repository 'maven' was added by plugin class 'FlutterPlugin'
比較的最近ののAndroid Studioでプロジェクトを新規作成するとFlutterと相性悪いみたいです。
以下を参考にsettings.gradle
とプロジェクトのbuild.gradle
を修正すればビルドができるようになると思います。
android - PluginApplicationException: Failed to apply plugin class 'FlutterPlugin' - Stack Overflow
iOSアプリへの追加
iOSアプリにFlutterモジュールを追加する
1. 既存iOSアプリのPodfileに以下の行を追加する
flutter_application_path = '../flutter_module_sample'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
flutter_module_sample/.ios/Flutter/podhelper.rb
のコードを読み込む処理です。
こちらはFlutterモジュールのflutter pub get
した際に自動で生成されます。
flutter_application_path
は作成したFlutterモジュールプロジェクトのルートを参照できるパスにしてください。
今回はiOSプロジェクトと同じ階層にあるので上記のようにしています。
target 'IOSApplicationSample' do
install_all_flutter_pods(flutter_application_path)
end
ビルドターゲットでpodhelper.rb
のinstall_all_flutter_pods
関数を呼びます。
これは引数で渡したパスにあるFlutterモジュールが依存しているすべてのプラグインをインストールする処理です。
2. プラグインをインストールする
Terminalを起動し、既存iOSアプリのルートフォルダまで移動する。
以下のコマンドを実行しFlutter関連および作成したFlutterプロジェクトが利用しているプラグインをインストールする。
% pod install
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_module_sample (0.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
なお、Flutterモジュール側でプラグインを追加し、flutter pub get
を行った場合、再度pod install
を実行する必要があります。
iOSアプリからFlutterモジュールを呼ぶ処理を実装する
1. AppDelegate.h
を修正する
- Flutterライブラリをインポートする
@import Flutter;
- 継承元をFlutterAppDelegateに変更する
@interface AppDelegate : FlutterAppDelegate
- FlutterEngineをプロパティに追加する
@property (nonatomic,strong) FlutterEngine *flutterEngine;
修正した例は以下のとおりです。
2. AppDelegate.m
を修正する
- 必要なライブラリをインポートする
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
- didFinishLaunchingWithOptionsでflutterEngineを初期化する
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine run];
// Used to connect plugins (only if you have plugins with iOS platform code).
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
AppDelegate.h
のflutterEngine
を初期化・起動しています。
initWithName
の引数は任意の値で大丈夫です。
3. 既存のソースコードからFlutterViewControllerを呼び出す
既存のソースコードにFlutter画面を起動する処理を追加します。
今回は単純にボタンが押下された際にFlutterViewController
を表示させています。
- 対象のViewControllerでFlutterライブラリとAppDelegateをインポート
@import Flutter;
#import "AppDelegate.h"
- Flutterの画面を開きたいアクションに以下の処理を実装
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
ここではAppDelegateのプロパティflutterEngine
を取得し、そのエンジンを使ってFlutterViewController
を生成、presentViewController
メソッドでFlutterの画面を開くという処理を行っています。
実装した例は以下のとおりです。
このサンプルでは、ボタンを用意しそのボタンが押下されたタイミングで前述の処理が実行されるようにしています。
実行
あとはXcodeから既存アプリを実行してみます。
既存のiOSネイティブアプリからFlutterの画面を起動し、正常に動作することを確認できました。
まとめ
以上で無事、ネイティブアプリからFlutterの画面を呼び出すことができました。
長くなってきたので、次の記事でFlutter画面のルート指定、iOSでのモーダルじゃない場合の開き方、ハマったことの解消法などを書こうと思います。
全体的に知識不足は否めないのでなにかお気づきのことがあればご指摘ください!