add-to-appのビルド統合
ソースからのビルドとビルド済みアーティファクト(AAR / XCFramework)の利用の違い、そしてそれぞれがCI/CDに与える影響を解説します。
コードをどこに置くかが決まったら、次の問題は、各ネイティブホストがビルド時にFlutterモジュールをどう取り込むかです。モードは2つあります。ソースからビルドするか、ビルド済みアーティファクトを利用するかです。この選択はCI/CDに直接影響します。ネイティブのビルドマシンにFlutterのツールチェーンが必要かどうかが、これで決まるからです。
2つのモード
ソースレベルの統合
ネイティブビルドが、モジュールの公開するフックを使い、自身のビルドの一部としてFlutterモジュールをDartのソースからコンパイルします。flutter create --template moduleがセットアップするのはこの方式で、フレームワークのサンプルもこれを使っています(Androidではinclude_flutter.、iOSではSwift Package Manager)。
- Androidでは、ホストの
settings.がモジュールをincludeし、モジュールのgradle .を適用します。これによりandroid/ include_flutter. groovy :flutterがGradleのサブプロジェクトとなり、アプリと一緒にビルドされます。 - iOSでは、Flutter 3.44以降、モジュールはSwift Package Managerで組み込まれます。モジュール内で
flutter build swift-package --platform iosを実行すると、build/配下にios/ SwiftPackages/ FlutterNativeIntegrationパッケージが生成されます。ホストはこのパッケージをXcodeプロジェクトに追加し、あわせてFlutterのiOSプロジェクトセットアップに記載されているflutter_integration.のスキームのプレアクションとビルドフェーズも追加します。3.44より前は、CocoaPodsが唯一の方法でした。ホストのsh Podfileがモジュールの.を読み込み、ios/ Flutter/ podhelper. rb install_all_flutter_podsを呼び出します。
# in the Flutter module - re-run whenever the module's dependencies change;
# Dart-only changes are rebuilt by Xcode
flutter build swift-package --platform iosiOS固有の点で、最初に知っておくべきことが2つあります。1つ目は、CocoaPodsは廃止に向かっていることです。FlutterはCocoaPodsのパスをメンテナンスモードで維持しており、CocoaPodsのレジストリは2026年12月2日に読み取り専用になります。そのため、新しい統合はSwiftPMで行い、既存のCocoaPodsのホストは移行を計画してください。Inlay自体はSwift packageとpodの両方で提供されています。2つ目は、Flutter 3.44の時点では、モジュールに対するflutter build swift-packageが、Package.を持たないプラグインをビルドするために内部で依然としてpod installを実行することです(flutter/flutter#184590)。そのため、ホストアプリがもうCocoaPodsを使っていなくても、モジュールをビルドするマシンにはCocoaPodsをインストールしておく必要があります。
SwiftPMのサポートが入るまでは、すでにネイティブアプリをCocoaPodsから移行済みの組織にとって、これは本当の障害でした。実際、CocoaPodsを断固として拒否したクライアントがあり、その場合の選択肢はビルド済みのXCFrameworkだけでした。現在その制約はなくなり、そのクライアントもその後SwiftPMによるソース統合に移行しています。
このモードの実際上の帰結は、ネイティブアプリをビルドするすべてのマシンにFlutter SDKのインストールが必要になることです。アプリのコンパイルにDartのコンパイルが含まれるようになるからです。
得られるのは、ホストが常に実際の最新のモジュールのソースに対してビルドされることです。境界をまたぐデバッグがエンドツーエンドで機能し、Dartを変更してからアプリで確認するまでの間に公開ステップがなく、特定のコミットのチェックアウトは完全に再現可能です。両側を所有するチームにとって、これは最もシンプルで速い開発ループです。
代償は、ツールチェーンが結合されることです。ネイティブのCIはFlutterを抱える必要があり、ネイティブのビルド時間はFlutterのビルド分だけ伸び、ネイティブ開発者のマシンにも合わせたバージョンのFlutterが必要になります。モノレポやサブモジュールの構成では自然に収まりますが、独立したネイティブチームにとっては、以前にはなかった摩擦になります。
ビルド済みアーティファクトによる統合
Flutterモジュールは事前にプラットフォームのバイナリにコンパイルされ、ネイティブビルドが取得できる場所に公開され、他のサードパーティ依存関係と同じように利用されます。
- Androidでは、
flutter build aarがAARとローカルのMavenリポジトリを生成します。ホストはそのリポジトリを追加し、座標とバージョンを指定してアーティファクトに依存します。 - iOSでは、
flutter build ios-frameworkがXCFramework(Flutterエンジン、あなたのDartコード、FlutterPluginRegistrant、そしてプラグインごとに1つ)を生成します。ホストはSwiftPM、CocoaPods、または直接これらを参照します。Flutterのドキュメントでは現在、このワークフローはレガシーなiOSプロジェクトセットアップとして扱われているため、パイプラインを構築する前に、使用するFlutterのバージョンで引き続き適切かを確認してください。
ここで得られるのは、ネイティブのビルドマシンにFlutterのツールチェーンが不要になることです。バイナリを取得するだけです。ネイティブビルドは速くなり、両側は本当の意味で分離されるので、ネイティブチームは他のSDKとまったく同じように、バージョン管理された依存関係としてFlutterを利用できます。ポリレポ構成を現実的にするのはこの方式です。
代償は、Dartの変更がアプリに反映されるまでの間に公開ステップが入ることです。変更のたびにアーティファクトをビルドし、バージョンを付けて公開し、ホスト側のバージョンを上げる必要があります(バージョニングを参照)。ホストが持つのはソースではなくバイナリなので、境界をまたぐデバッグは難しくなります。また、ホストが必要とするすべてのバリアントをビルドして公開しなければなりません。ホストのビルドは同じビルドモードのアーティファクトしかリンクできないため、debug/profile/releaseのセット(product flavorがあればflavorごと)が必要です。ただし、ABIごとに分ける必要はありません。flutter build aarは、ビルドモードごとに1つのAARにすべてのAndroid ABIを含めます。
モードの選び方
| ソースレベル | ビルド済みアーティファクト | |
|---|---|---|
| ネイティブCIでのFlutter SDK | 必要 | 不要 |
| Dartの変更 → アプリへの反映 | 即時 | ビルド+公開+バージョン更新 |
| 境界をまたぐデバッグ | 完全 | 限定的(バイナリ) |
| ネイティブのビルド時間 | 遅い(Dartをビルド) | 速い(バイナリをリンク) |
| 自然なリポジトリ構成 | モノレポ / サブモジュール | ポリレポ |
選択はリポジトリ構成とほぼ連動します。モノレポやサブモジュールの構成では、ソースレベルが自然なデフォルトです。よりシンプルで、公開の往復がなくなります。アーティファクトによる統合はポリレポを分離する手段であり、ネイティブチームが自分たちのツールチェーンのままでいられます。推奨する最終形はモノレポなので、ほとんどのプロジェクトは最終的にソースレベルの統合に落ち着くべきです。組織の境界、ビルド時間の分離、あるいはマシンにFlutterを入れていないネイティブチームといった事情で、公開のオーバーヘッドを払ってでも分離する価値がある場合に、アーティファクトを選んでください。
ハイブリッドアプリのCI/CD
どちらのモードを選んでも、ハイブリッドアプリのパイプラインはどこかでFlutterのビルドを考慮する必要があります。その形はモードによって異なります。
ソースレベルの統合では、たまたまFlutterを含む1つのネイティブビルドがあるだけです。
- Flutter SDKは、ネイティブのビルドエージェントにプロビジョニングし、バージョンを固定する必要があります。XcodeやAndroid SDKと同じ方法で固定してください。Flutterのバージョンがずれると、不安定で再現の難しいビルドにつながります。
flutter pub getとコード生成(inlay_gen/build_runner)はネイティブビルドの前に実行し、Gradle/Xcodeがコンパイルする時点で生成された境界のコードが存在するようにする必要があります。- Flutterのビルド成果物(エンジン、コンパイル済みのDart)は、実行間でキャッシュする価値があります。そうすれば、ネイティブビルドが毎回Dartをゼロから再コンパイルせずに済みます。
アーティファクトによる統合では、実質的に2つのパイプラインがあります。
- モジュールのパイプラインが、AAR / XCFrameworkをビルドし、バージョンを付け、アーティファクトレジストリ(Mavenリポジトリ、パッケージレジストリ、またはgitのリリース)に公開します。Flutterが必要なのはここだけです。
- 各ホストのパイプラインは、公開されたアーティファクトをバージョン指定で解決し、Flutterのツールチェーンなしでビルドします。
- 両者はバージョニングでつながります。 新しいモジュールのバージョンを公開し、ホストをそのバージョンに上げることが受け渡しであり、これは手作業ではなく自動化すべきです。
どちらの場合にも当てはまるルールがいくつかあります。
- ビルドモードを合わせる。
releaseのホストはreleaseのFlutterアーティファクトをリンクします。debug/profile/releaseを混在させることはできません。アーティファクトによる統合での不具合の多くは、モードの不一致に起因します。 - Flutterだけのチェックは分離し、高速に保つ。
dart analyze、dart test、コード生成は、遅いネイティブビルドとは独立して、モジュールが変更されるたびに実行されるFlutter側のジョブに置きます。こうすることで、Dartのリグレッションはアプリ全体のビルド後ではなく、数秒で表面化します。 - 生成コードはビルドステップとして扱う。 チェックインされた
*.ファイルが最新であることに頼るのではなく、CIで境界のコードを再生成する(または最新であることを検証する)ようにしてください。g. *
アーティファクトによる統合がもたらす公開とバージョン更新の受け渡しは、実際にはバージョニングの問題です。つまり、ホストがどのモジュールバージョンに固定されていて、それをどう進めていくかという問題です。これについてはバージョニングで個別に扱っています。