はじめに
既存のAndroidアプリにFlutterで作った画面を追加することができます。
「Add-to-app」という機能を使うことで、ネイティブアプリ(Android, iOS)にFlutterで開発した画面やロジックを追加することができます。
Add-to-appはFlutter 1.12以降で正式に安定版に導入されているので、最新のFlutter Stableバージョンでも利用できます。
今回は実際にAdd-to-appを使用して、Android(kotlin)のネイティブアプリから、Flutterで開発した画面を呼び出してみたいと思います。
サンプルアプリ
native_android_app_with_flutter
├── android_app // androidネイティブアプリ
└── flutter_module // Flutterで開発した画面があるモジュール
Androidアプリ作成 【Androidアプリ側】
画面遷移するためのボタンがあるアプリを作成します。
※今回はJetpack Compose使用
Flutterのモジュール作成 【Flutterモジュール側】
Flutterのバージョンを最新に上げる(必要に応じて)
flutter upgrade
Flutterのモジュール作成
以下のコマンドを実行してflutter_module
を作成します。
このモジュール内にFlutterで開発した画面やロジックを配置します。
flutter create -t module flutter_module
AndroidアプリでFlutterモジュールを使えるようにする
Flutterモジュールを汎用的なローカル Maven リポジトリとして使用するためにパッケージ化します。
汎用ローカル Maven リポジトリは、ビルド済みの Android ライブラリ(AARファイル)と、ライブラリの依存関係や設定を記述したPOMファイルで構成されています。
/flutter_module
で以下のコマンドを実行します。
flutter build aar
!エラー発生
以下のエラーが発生、gradleがサポートしていないjavaを使用していた模様。
FAILURE: Build failed with an exception.
* What went wrong:
※省略
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 65
java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment Homebrew (build 21.0.5)
OpenJDK 64-Bit Server VM Homebrew (build 21.0.5, mixed mode, sharing)
flutter_module/.android/gradle/wrapper/gradle-wrapper.properties
で以下のようにgradleのバージョン変更
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
↓
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
ビルド成功
flutter_module % flutter build aar
Running Gradle task 'assembleAarDebug'... 109.4s
✓ Built build/host/outputs/repo
Running Gradle task 'assembleAarProfile'... 31.3s
✓ Built build/host/outputs/repo
Running Gradle task 'assembleAarRelease'... 29.9s
✓ Built build/host/outputs/repo
Consuming the Module
1. Open <host>/app/build.gradle
2. Ensure you have the repositories configured, otherwise add them:
String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url '/../w2406/native_android_app_with_flutter/flutter_module/build/host/outputs/repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
}
3. Make the host app depend on the Flutter module:
dependencies {
debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}
4. Add the `profile` build type:
android {
buildTypes {
profile {
initWith debug
}
}
}
To learn more, visit https://flutter.dev/to/integrate-android-archive
Flutterモジュールの依存関係を追加 【Androidアプリ側】
以下の依存関係を追加します。
-
maven("https://storage.googleapis.com/download.flutter.io")
Dart/Flutterランタイムや関連する依存関係を取得するために必要です。 -
maven(url = "../../flutter_module/build/host/outputs/repo")
Flutterモジュールを依存関係に追加して、Androidアプリ内から使用できるようにします。
settings.gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://storage.googleapis.com/download.flutter.io") ←追加
maven(url = "../../flutter_module/build/host/outputs/repo") ←追加
}
}
Flutter画面のActivityを定義【Androidアプリ側】
AndroidアプリにFlutter画面を表示するために、画面(Activity)をAndroidManifest.xml
に定義します。
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize" />
@style/LaunchTheme
のFlutterActivityの起動時のテーマを作成しておきます。
res/values/style.xml
<resources>
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>
AndroidアプリからFlutter画面を呼び出す【Androidアプリ側】
画面内でcontext.startActivity
を実行することで、Flutter画面をActivityとして呼び出すことができます。
@Composable
fun PageOnFlutterButton() {
val context = LocalContext.current
Button(onClick = {
// 遷移処理
context.startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/")
.build(this)
)
}) {
Text("Flutterで作成された画面")
}
}
※(任意)アプリ内で1つのFlutterEngineを使用する
上記のようにFlutterEngineを呼び出すと、毎回FlutterEngineを作成されます。
これにより、リソース不足が発生する原因になったり、Flutter画面を表示するときに初回表示が遅くなります。
これらを解消するために、1つのFlutterEngine
をキャッシュ内に保持しておいて、使い回すようにします。
// FlutterEngineのキャッシュキー
const val flutterEngineId = "flutter_engine_id"
class MainActivity : ComponentActivity() {
private lateinit var flutterEngine: FlutterEngine
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 毎回FlutterEngineが作られることで、リソースが無駄・起動が遅くになるのでFlutterEngineをキャッシュに保持
// Flutterエンジンを事前にインスタンス化
flutterEngine = FlutterEngine(this)
// FlutterEngineに初期ルートを設定
flutterEngine.navigationChannel.setInitialRoute("/");
// FlutterEngineの初期化
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// FlutterEngineを保持して使いまわせるようにする
FlutterEngineCache
.getInstance()
.put(flutterEngineId, flutterEngine)
setContent {...}
省略..
}
@Composable
fun PageOnFlutterButton() {
val context = LocalContext.current
Button(onClick = {
// 遷移処理
context.startActivity(
FlutterActivity
.withCachedEngine(flutterEngineId)
.build(context)
)
}) {
Text("Flutterで作成された画面")
}
}
まとめ
Add-to-appを使用して、Android(kotlin)のネイティブアプリから、Flutterで開発した画面を呼び出すことができました。実際に試してみた感想としては、結構簡単にFlutterで開発された画面を呼び出すことができました。
「ネイティブアプリをFlutterで一部分リプレース→すべてリプレース」や「ネイティブアプリの新機能・画面をFlutter開発」に活用できると思いました。