LoginSignup
10

More than 1 year has passed since last update.

Flutterで既存のiOS / Androidアプリに機能追加 (Add-to-app):基本編

Last updated at Posted at 2022-02-07

はじめに

お仕事で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を使用しない場合は公式の手順を参考にしてください。

  1. Android Studio > File > New Flutter Project... でFlutterプロジェクト作成ウィザードを開く
    image.png

  2. Flutter SDKのパスが正しいことを確認してNextで進む
    image.png

  3. 任意のプロジェクト名、パスを指定し、Project typeModuleに設定し、Finishで完了する
    image.png

  4. デフォルトのFlutterモジュールが生成される
    image.png

  5. この状態でflutter pub getを実行しておく
    image.png

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した際に自動で生成されます。

追加した例は以下の通りです。
image.png

2. 既存Androidアプリのapp/build.gradleを修正する

dependenciesに以下の行を追加します。

implementation project(':flutter')

追加した例は以下の通りです。
image.png

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にアクションバーなしのテーマを適用しています。
image.png

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()));
}

追加した例は以下の通りです。
image.png

実行

あとはAndroid Studioから既存アプリを起動してみます。
111wb-16608.gif

既存の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.rbinstall_all_flutter_pods関数を呼びます。
これは引数で渡したパスにあるFlutterモジュールが依存しているすべてのプラグインをインストールする処理です。

image.png

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.

image.png

なお、Flutterモジュール側でプラグインを追加し、flutter pub getを行った場合、再度pod installを実行する必要があります。

iOSアプリからFlutterモジュールを呼ぶ処理を実装する

1. AppDelegate.hを修正する

  • Flutterライブラリをインポートする
    @import Flutter;
  • 継承元をFlutterAppDelegateに変更する
    @interface AppDelegate : FlutterAppDelegate
  • FlutterEngineをプロパティに追加する
    @property (nonatomic,strong) FlutterEngine *flutterEngine;
    修正した例は以下のとおりです。 image.png

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.hflutterEngineを初期化・起動しています。
initWithNameの引数は任意の値で大丈夫です。

修正した例は以下のとおりです。

image.png

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の画面を開くという処理を行っています。

実装した例は以下のとおりです。
このサンプルでは、ボタンを用意しそのボタンが押下されたタイミングで前述の処理が実行されるようにしています。
image.png

実行

あとはXcodeから既存アプリを実行してみます。
ql84j-kb9f0.gif
既存のiOSネイティブアプリからFlutterの画面を起動し、正常に動作することを確認できました。

まとめ

以上で無事、ネイティブアプリからFlutterの画面を呼び出すことができました。
長くなってきたので、次の記事でFlutter画面のルート指定、iOSでのモーダルじゃない場合の開き方、ハマったことの解消法などを書こうと思います。

全体的に知識不足は否めないのでなにかお気づきのことがあればご指摘ください!

参考

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
10