🦔

Android APIアップデートで詰まって気づいた、Flutterアプリのアーキテクチャ理解の重要性

に公開

はじめに

FlutterアプリケーションのAndroid API Level 35への移行作業において、想定以上に複雑な問題に直面し、当初は単純なバージョン更新作業と考えていたものが、最終的にはビルドシステム全体の理解を必要とする大規模な作業となりました。

マルチプラットフォーム開発の魅力はネイティブ側をあまり意識せず動作してくれる抽象化にありますが、その抽象化層で問題が発生した際、影響範囲が広範囲に及ぶため、独力で解決するにはFlutterのマルチプラットフォームアーキテクチャへの深い理解が不可欠であることを痛感しました。

本記事では、私の体験を元に移行作業で直面した具体的な問題と、その過程で調査したFlutterアプリケーションのアーキテクチャ理解について共有します。
同様の課題に直面する開発者の参考になれば幸いです。

背景

Google Play ConsoleからAndroid APIアップデートの警告が表示され、targetSdkVersion 35への移行要求が表示されました。
2025年8月31日までの対応が必須となったため、8月末リリースを目標に8月初旬から対応を始めましたが、結果的に対応期限を伸ばして9月第1週リリースとなりました。

過去の移行経験から一定の複雑性は想定していましたが、
実際にはビルドシステム全体に波及する大規模な変更となり、想定を大幅に上回る工数が必要となりました。

影響調査

まずは影響調査から始めました。
Android開発者向けの公式ドキュメントを読み込み、API Level 35での変更点を確認しました。

確認した結果、主な影響としては以下3点でした。

  • 通知リスナーの制限
  • PendingIntentの明示要件強化
  • Edge-to-edge enforcement(全画面表示の強制)

これらの変更点を把握したところで影響調査を完了としてしまい、Android側の対応だけで済むと考えていました。
しかし、実際の作業では想定外の問題が次々と発生しました。

発生した技術的課題の詳細

targetSdkVersionを35に更新した結果、以下の4つの大きな課題に直面しました。

  1. Namespace定義の必須化に伴うビルドエラー
    Android Gradle Plugin 8.xでの新要件により、すべてのモジュールでnamespace定義が必要になりました

  2. ビルドツールの連鎖的なアップデート
    Gradle、Kotlin、Flutter SDKなど、ビルドシステム全体の更新が必要になりました

  3. サードパーティパッケージの非互換性
    使用していた複数のパッケージがnamespace未対応、またはメンテナンス停止状態でした

  4. Android側の変更がiOS側にも影響
    Flutterパッケージの更新により、iOS側でもビルドエラーが発生しました

これらの課題は相互に関連しており、一つの解決が別の問題を引き起こす連鎖反応となりました。

1. Namespace定義の必須化に伴うビルドエラー

targetSdkVersionを35に更新した直後、以下のビルドエラーが大量に発生しました。

Namespace not specified. Specify a namespace in the module's build file.

調査の結果、Android Gradle Plugin 8.x系では、すべてのAndroidモジュール(アプリ本体と依存ライブラリ)でnamespaceという新しい設定項目を明示的に定義することが必須となっていました。

以前はapplicationId(アプリの識別子)がリソース管理の名前空間も兼ねていましたが、
AGP 8.xからはこれらが分離され、別々に管理される仕組みに変更されていました。
さらに、この変更に対応するためには、Gradle、Kotlin、その他のビルドツールも連鎖的にアップデートする必要があり、プロジェクト全体のビルドシステムを根本から見直す必要がありました。

2. 導入パッケージの依存関係問題

targetSdkVersion 35への更新に伴い、最新のビルドツールとの互換性確保のためAndroid Gradle Plugin 8.x系へのアップグレードが必要となりました。
これにより以下の連鎖的なバージョン更新が発生しました。

The Android Gradle plugin supports only Kotlin Gradle plugin version 1.9.0 and higher
  • Android Gradle Plugin: 7.1.2 → 8.1.1
  • Gradle Wrapper: 7.4 → 8.2
  • Kotlin: 1.9.0 → 2.0.21

各ツールの相互依存関係により、部分的な更新では整合性が取れない状況となりました。

3. サードパーティパッケージの非互換性

プロジェクトで使用していた複数のパッケージがnamespace未対応となっていることが分かりました。

> A problem occurred configuring project ':package_info'.
> Namespace not specified.

さらに調査を進めると、以下のパッケージが既にdeprecatedまたはメンテナンス停止状態であることも判明しました。

  • package_info
  • flutter_app_badger
  • uni_links

4. Android側の変更がiOS側にも影響

Android側の修正にも関わらず、iOS側でビルドエラーが発生しました。

Module 'adjust_sdk' not found

FlutterパッケージのバージョンアップデートがiOS側のネイティブ依存関係にも影響を与え、CocoaPodsの依存解決に失敗する事象が発生しました。

エラー解決の困難さとFlutterアーキテクチャの理解の必要性

エラーの原因がわからなかったため、なかなか解決できず1つのエラーに2,3日費やすこともありました。

例えば「Module 'adjust_sdk' not found」というエラーに直面した際、当初使用していたバージョンには既知の不具合があることがわかり、修正が含まれている最新バージョンに更新しましたが、それでも解決せず原因が全くわかりませんでした。

結果として原因はビルド環境のCPUアーキテクチャ(arm64)に関する問題でしたが、当時は全く見当もつかず、同様の事象でビルドができない記事も見つからなかったため、解消に3日もかかりました。

これらの問題に直面し、以下の本質的な疑問が浮上しました。

  • なぜAndroidのビルド設定変更がiOSビルドに影響するのか
  • namespace定義がビルドプロセスに与える直接的な影響は何か
  • Flutterのビルドシステムはどのような階層構造で動作しているのか

表層的なエラー対処では限界があり、Flutterアプリケーションのアーキテクチャを体系的に理解する必要性を認識しました。

Flutterアプリケーションのアーキテクチャから課題の根本原因を深掘る

レイヤーアーキテクチャの全体像の理解

表層的なエラー対処を繰り返しても解決できない問題に直面し、根本的な理解が必要だと痛感しました。
なぜAndroidの変更がiOSに影響するのか、なぜ一つの修正が別の問題を引き起こすのか、これらの疑問に答えるため、Flutterアプリケーションの全体構造を理解することにしました。

Flutterの公式ドキュメントや技術資料を調査した結果、Flutterアプリケーションは6層構造で構成されていることがわかりました。
この理解により、エラーがどの層で発生し、なぜ他の層に影響するのかが明確になりました。

┌─────────────────────────────────────────────┐
│     Application Layer                       │ ← Dartアプリケーションコード
├─────────────────────────────────────────────┤
│     Flutter Framework                       │ ← Widget/Rendering/Animation
├─────────────────────────────────────────────┤
│     Flutter Engine (C++)                    │ ← Dart VM/Skia/Platform Channels
├─────────────────────────────────────────────┤
│     Native Build Infrastructure             │ ← Xcode/Gradleなどビルド環境
│     ・iOS: Xcode/CocoaPods/Swift            │   ※ 問題発生層
│     ・Android: Gradle/AGP/Kotlin            │
├─────────────────────────────────────────────┤
│     Platform Runtime                        │ ← UIKit/ART
├─────────────────────────────────────────────┤
│     Hardware/OS Layer                       │ ← iOS/Android OS
└─────────────────────────────────────────────┘

今回の問題は、Native Build Infrastructure層で発生し、その影響が上位層に波及する形となりました。

ビルドシステムの理解

今回の問題の多くがNative Build Infrastructure層で発生していたため、
この層の主要コンポーネントであるGradleとCocoaPodsの役割を詳しく調査しました。

Gradleビルドシステムの役割と影響範囲

調査の結果、Gradleは単純な設定管理ツールではなく、Androidアプリケーションのビルドプロセス全体を統括する「現場監督」的な役割を担っていることがわかりました。
その主要な責務と今回の影響を、私なりに以下のように整理しました。

フェーズ 処理内容 発生した問題
依存関係解決 Maven/Google Repositoryからライブラリを取得・管理※ namespace未対応パッケージで解決失敗
コンパイル Kotlin/JavaソースコードをDEXバイトコードに変換 Kotlinバージョン不整合によるコンパイルエラー
パッケージング APK/AABファイルの生成と署名 前段階の失敗により実行不可

※Maven/Google Repositoryはライブラリの「倉庫」のようなもので、Gradleが必要なライブラリを自動でダウンロードしてきます。

CocoaPodsの役割とiOSビルドエラーの根本原因

CocoaPodsの役割を理解した結果、iOSで発生していた「Module ‘adjust_sdk’ not found」エラーの原因を把握することができました。
また、CocoaPodsはGradleと同様に、依存関係管理とビルドプロセス統合 を担う重要な仕組みであることが確認できました。
その主要な責務と今回発生したエラーの根本原因を以下のように整理しました。

CocoaPodsの主な役割は以下の3つです。

  1. 依存関係管理 - Podfileに記載されたライブラリの依存関係を解決し、適切なバージョンをダウンロード
  2. ワークスペース統合 - ダウンロードしたライブラリをXcodeプロジェクトに統合し、ビルド可能な状態にする
  3. アーキテクチャ対応 - 各ライブラリがサポートするCPUアーキテクチャ(arm64、x86_64など)を管理

今回のエラーは、Adjust SDKのライブラリがM1 Mac(arm64アーキテクチャ)のビルド環境に対応していなかったことが原因でした。
パッケージを最新バージョンに更新しても解決しなかったのは、ビルド環境のアーキテクチャとライブラリの対応アーキテクチャの不一致という、より根本的な問題だったためです。

根本的な原因:CPUアーキテクチャの不一致

先述の「Module 'adjust_sdk' not found」エラーの根本原因は、CPUアーキテクチャの不一致にありました。

CPUアーキテクチャとは「CPU(プロセッサ)が理解できる命令の種類」のことで、主に以下の2種類があります。

  • arm64:ARM系CPUが使う64bit命令セット(スマホ・M1 Macなど)
  • x86_64:Intel系CPUが使う64bit命令セット(PC・エミュレータなど)

M1 Mac(arm64)でiOSシミュレータを動かす際、一部のライブラリがx86_64向けにしかビルドされていない場合、アーキテクチャの不一致でビルドエラーが発生します。

今回のケースでは、Adjust SDKがx86_64のみに対応していたため、M1 Macのビルド環境でエラーが発生しました。
この問題を理解できたことで、Podfileで特定のアーキテクチャを除外する設定を追加するという解決策にたどり着くことができました。

マルチプラットフォーム依存関係の構造

Android側の変更がiOSビルドに影響を与えた理由は、Flutterパッケージの構造に起因します。

# pubspec.yaml - 統一された依存関係管理
dependencies:
  flutter_package: ^1.0.0  # 単一バージョンで両プラットフォーム対応

パッケージ内部の構造

flutter_package/
├── lib/              # Dart実装(プラットフォーム共通)
├── android/          # Android固有実装
│   ├── build.gradle  # namespace未対応による問題発生源
│   └── src/
└── ios/              # iOS固有実装
    ├── Classes/      # バージョン更新による非互換性
    └── flutter_package.podspec

この構造により、単一パッケージのバージョン更新が両プラットフォームに影響を及ぼします。Android対応のためのバージョンアップが、iOS側のネイティブAPIの非互換性を引き起こす結果となりました。

Flutterアーキテクチャ理解後の変化

Flutterアプリケーションの階層構造を理解したことで、それまで3日かかっても解決できなかったエラーが、数時間で解決できるようになりました。

「Module 'adjust_sdk' not found」エラーは、エラーメッセージをそのままGoogle検索し、Stack Overflowで見つけた「最新バージョンに更新する」「Podfile.lockを削除する」「pod installをやり直す」といった対処法を片っ端から試していました。
しかし、どれも効果がなく、3日間も同じエラーと格闘していました。

Flutterの6層構造を理解した後、このエラーを改めて見直すと、Native Build Infrastructure層の問題であることがわかりました。
さらにCocoaPodsの役割を理解したことで、これは単なるライブラリの不具合ではなく、CPUアーキテクチャの不一致が原因である可能性に気づきました。

実際に調査すると、M1 Mac(arm64)でビルドしているのに、Adjust SDKがx86_64のバイナリしか提供していないことが判明しました。
この理解があったからこそ、Podfileにconfig.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'を追加することで、シミュレータビルド時にarm64アーキテクチャを除外し、x86_64でビルドする解決策を導き出すことができました。

また、namespace関連のエラーについても、以前なら「なぜAndroidの設定変更でこんなに多くのエラーが出るのか」と途方に暮れていたところ、Gradleの役割を理解したことで見え方が変わりました。
Gradleが依存関係解決、コンパイル、パッケージングという3つのフェーズを統括していることを知り、namespace未対応のパッケージが依存関係解決フェーズで失敗し、それが連鎖的に他のフェーズにも影響することが理解できました。

この理解により、エラーメッセージに振り回されることなく、「まず依存関係の問題を解決し、次にコンパイルエラーに対処する」という戦略的なアプローチが取れるようになりました。

まとめ

Android API Level 35への移行は、Android側の影響調査だけでは不十分で、Flutter全体のアーキテクチャに関わる大規模な作業となりましたが、Flutterアプリケーションの内部構造を深く理解する良い機会となりました。

改めて技術的な問題に直面した際、表面的な対処に終始するのではなく、アーキテクチャレベルで理解することの重要性を痛感しました。
マルチプラットフォーム開発において、「なぜこうなっているのか」を理解することは、効率的な問題解決と安定したアプリケーション開発の基盤となると思いました。

システムアップデートは重たく難しいタスクとして嫌悪されがちですが、実はシステムの深層を理解するための貴重な入口になっていると思いました。
今回の経験を通じて得た知見を活かし、プロダクトの継続的な成長のために、今後もこうした技術的な挑戦に積極的に取り組んでいきたいと考えています。

参考文献

本記事の執筆にあたり、以下の技術文書を参照しました:

ASSIGN

Discussion