はじめに
現在、方言を話すおしゃべり猫型ロボット「ミーア」を開発中。
今まで、ミーアのアプリは、iOSのシミュレーターと実機でのみ動作確認をしていたが、Android emulatorでももちろん確認しないといけない。
対応していくうちに、「そもそもbuild.gradleって何?」となったので、基本をおさらい。
Gradleとは何か
Gradleとは、Groovy(またはKotlin)をベースにしたビルド自動化システム。ソフトウェアのコンパイル、パッケージング、テスト、デプロイなど、開発プロセスの自動化を担う。
https://docs.gradle.org/current/userguide/userguide.html
ビルドスクリプトはDSL(ドメイン固有言語:特定の問題領域(ドメイン)に特化したプログラミング言語や仕様言語)を用いて記述されるので、ビルドプロセスの高度なカスタマイズを可能にする。DSLを通じて、依存関係の管理、ビルドタスクの定義、アプリケーションのパッケージングなどを行える。設定は、build.gradle
に書く。
Flutterアプリでは、Dartで記述されたコードがネイティブバイナリにコンパイルされる。FlutterプロジェクトにおいてGradleは、最終的なアプリケーションパッケージ(APK)を生成する際、ネイティブ部分のコンパイルや各種リソースの統合、依存関係の解決などを行う。
ちなみに、”Gradle”の名前は「Gradual」(徐々に)と「Build」(ビルド)の組み合わせから来ている。つまり、Gradleは段階的にビルドを進めることができるビルドシステムを意味している。
Flutterプロジェクトのbuild.gradleファイル
Flutterプロジェクトには、通常2つのbuild.gradle
ファイルが存在する。
- プロジェクトレベル:Androidアプリ全体の設定を行う。
- アプリレベルのファイル:アプリモジュールの設定を行う。
階層構造は下記
└── MyApp/ # Project
├── gradle/
│ └── wrapper/
│ └── gradle-wrapper.properties
├── build.gradle(.kts) #プロジェクトレベルのgradleファイル
├── settings.gradle(.kts)
└── app/ # Module
├── build.gradle(.kts) #アプリレベルのgradleファイル
└── build/
├── libs/
└── src/
└── main/ # Source set
├── java/
│ └── com.example.myapp
├── res/
│ ├── drawable/
│ ├── values/
│ └── ...
└── AndroidManifest.xml
Flutter3.16以降のgradle記載方法の変更点
ちなみに、このプロジェクトレベルとアプリレベルのgradleファイルの記載方法だが、Flutter3.16以降で大きな変更があった。
- Flutter 3.16では、Gradleの宣言的なplugins {}ブロック(Plugin DSLとも呼ばれる)を使ってこれらのプラグインを適用するサポートが追加され、こちらが推奨される記載方法になった。
- Android Gradle Plugin(AGP)とKotlinのバージョン指定が
build.gradle
からsettings.gradle
に移動した。
詳しくは下記参照。
https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply
というわけで、setting.gradle→build.gradle(project)→build.gradle(app)の順に見ていく。
android/settings.gradle:Gradle設定ファイル
この設定ファイルはルートプロジェクトディレクトリにあり、プロジェクトレベルのリポジトリ設定を定義し、アプリのビルド時に含めるモジュールを Gradle に伝える。
- プラグイン管理 (
pluginManagement
): Flutter SDKパスとプロジェクトで使用するリポジトリ(google()
,mavenCentral()
など)を設定。 - プラグイン定義 (
plugins
): 必要なGradleプラグイン(Flutterプラグイン、Androidプラグイン、Kotlinプラグインなど)を宣言的に定義。バージョン管理もここで行い、apply false
を指定して自動適用を防ぐ。 - モジュールのインクルード (
include ":app"
): ビルド対象のモジュールを指定。
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.4.2" apply false
id "org.jetbrains.kotlin.android" version "1.9.20" apply false
id "com.google.gms.google-services" version "4.3.14" apply false
}
include ":app"
プロジェクトレベルのbuild.gradle(/project_root/android/build.gradle)
プロジェクト全体のビルド設定を定義する。
- リポジトリ定義 (
allprojects { repositories { ... } }
): 全プロジェクト共通のリポジトリを定義。 - クリーンタスク (
tasks.register("clean", Delete) { ... }
):clean
タスクを定義し、プロジェクトのビルドディレクトリを削除。 - AGPやKotlinのバージョン定義が削除され、これらは
settings.gradle
に移行。
// /project_root/android/build.gradle
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
アプリレベルのbuild.gradle(/project_root/android/app/build.gradle)
特定のアプリケーションモジュール(通常はFlutterアプリ自体)に対する設定を管理する。
主な責務
- プラグインの適用 (
plugins { ... }
): 必要なプラグインを宣言的に適用。ここではプラグインを実際にアクティブにする。 - アプリ固有の設定 (
android { ... }
): SDKのバージョン、ターゲットバージョン、依存関係など、アプリ固有の設定を行う。 - 依存関係の管理 (
dependencies { ... }
): アプリレベルで必要な依存ライブラリを管理。
plugins {
id 'com.android.application'
id 'com.google.gms.google-services'
id 'kotlin-android'
id "dev.flutter.flutter-gradle-plugin"
}
android {
compileSdkVersion 34 // コンパイルに使用するSDKバージョン
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "${dartDefines.appId}" //アプリの一意の識別子。Google Playストアなどのアプリストアでアプリを一意に識別するために使用される
minSdkVersion 26 // サポートする最小のAndroidバージョン
targetSdkVersion flutter.targetSdkVersion // アプリが最適化されていると想定するAndroidバージョン
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
resValue "string", "app_name", "${dartDefines.appName}"
archivesBaseName "${dartDefines.flavor}-${flutterVersionName}"
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
flutter {
source '../..'
}
この、アプリレベルのbuild.gradle
ファイル名で記述されるアプリ固有の設定 (SDKのバージョン、ターゲットバージョン、依存関係など)について、より詳しく見ていく。
SDKバージョンとAPIレベルの設定(アプリレベル)
- SDKバージョンとAPIレベルの設定はアプリ単位で行うため、これらの設定は
app/build.gradle
ファイルに記述する。 compileSdkVersion
: コンパイル時に使用されるAPIレベル。、ソースコードのコンパイル時に使用可能な Android API と Java API が決まる。最新の Android 機能を使用するには、コンパイル時に最新の Android SDK を使用する。minSdkVersion
: アプリがインストール可能な最低限のAndroidバージョン(APIレベル)。この値を設定することで、より古いバージョンのデバイスではアプリがインストールさえできないように制限できる。targetSdkVersion
: アプリが最適化およびテストされている目標のAndroidバージョン(APIレベル)を指定する。compileSdkVersionがアプリが利用可能なAPIの最大セットを定義するため、targetSdkVersionは必ずそれ以下、または同じ値でなければないが、compileSdk
とtargetSdk
の値を同じにする必要はない。targetSdk
はcompileSdk
以下で指定する。
Flutterの場合、compileSdkVersion
をflutter.compileSdkVersion
のように指定して、Flutter SDKによって提供されるバージョンに動的に設定されるようにするのが一般的。
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.myapp"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 26
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
この場合、targetSdkVersion
の値を確認するには、以下の手順を実行する。
local.propertiesファイルでflutter.sdkの値を確認
project_root/android配下にあるlocal.properties
ファイルを開き、flutter.sdk
の値を確認する。これはFlutter SDKがインストールされているディレクトリを指す。
Flutter SDK内のflutter.groovy
ファイルを開く
/usr/local/Caskroom/flutter/3.7.12/flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy を開く。
- compileSdkVersion = 34
- targetSdk Version = 33
が設定されていることが分かった。
ちなみに今回の記事の趣旨からはズレるが、compileSdkVersionで設定されているAPIレベルに相当するエミュレーターをインストールするには、
Android Studioで、Toolsタブ→SDK Managerをクリックして、”SDK Platforms”タブを選択し、相当するAPIレベルを選択する(今回だとAPI34)。その後、画面右上のDevice Managerアイコンをクリックして、emulatorを追加する。
gradle-wrapper.properties
このファイルは、Gradle Wrapperに関する設定が書かれており、プロジェクトのビルドに使用するGradleのバージョンを指定する。Gradle Wrapperは、開発者が自分のマシンにGradleをインストールしていなくても、指定されたバージョンのGradleを自動的にダウンロードし、プロジェクトのビルドを行えるようにするもの。
gradle-wrapper.properties
ファイル内の設定例。
distributionUrl
はダウンロードするGradleのバージョンとタイプ(binまたはall)を指定する。- 他のパラメータは、ダウンロードしたファイルの保存場所や方法を定義。
// /project_root/android/gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
GradleバージョンとJava, KotlinのCompatibilityはこちら
https://docs.gradle.org/current/userguide/compatibility.html
Gradleのビルドキャッシュクリアと、再ビルド
Androidエミュレーターでビルドする時に、エラーが生じた場合で、指摘されているエラーがコード上は問題ない場合はビルドキャッシュの問題の可能性がある。
your-flutter-project/android/
ディレクトリで、gradleのビルドをキャッシュした後に、プロジェクトを再ビルドする。
./gradlew clean
./gradlew build
# つなげて記載も可能
./gradlew clean build
ビルド時にエラー詳細などを見たいときは、下記オプションをつける。
--stacktrace
または-S
:エラーの原因を特定するためにスタックトレースを表示--info
または-i
:ビルドプロセスの詳細情報を提供--debug
:さらに詳細なデバッグ情報を提供
flutterパッケージを追加する時
何かしら新しい機能をFlutterアプリに追加する場合、まずは、パッケージのReadmeをよく読む。
例えば、バックグラウンドでアプリ操作をしたい場合に、flutter_background_service
というflutterパッケージを導入したいとする。
https://pub.dev/packages/flutter_background_service
まず、Readmeを読むと、Androidの場合のWarningとして下記が記載されている。
WARNING:
Please make sure your project already use the version of gradle tools below:
in android/build.gradle classpath 'com.android.tools.build:gradle:7.4.2'
in android/build.gradle ext.kotlin_version = '1.8.10'
in android/gradle/wrapper/gradle-wrapper.properties distributionUrl=https://services.gradle.org/distributions/gradle-7.5-all.zip
flutter_background_service
パッケージを使用する際に、互換性を保つために推奨される設定をしないまま開発すると、実際に動作確認の際に「あれ、動かない!?なんで」となって原因究明に時間を費やす羽目になる。
ローカルホスト上のサーバーとの通信には、10.0.2.2を指定
Androidエミュレータを使用してローカルホスト上で動作しているサーバーと通信する際には、通常の localhost
アドレス (127.0.0.1) を使用することはできない。これは、エミュレータが実行される環境がホストマシンと異なるため。
10.0.2.2を指定することで、エミュレータからホストマシン上で実行されているサーバー(APIサーバー、データベースサーバーなど)への接続が可能になる。
例えば、ホストマシン上で8080ポートでAPIサーバーが動作している場合、エミュレータ上のアプリケーションからこのAPIサーバーに接続するためのURLは次のようになる。このIPアドレスはAndroidエミュレータ専用であり、Android実機でのテストでは異なる設定が必要になる。
API_URL=http://10.0.2.2:8080
FlutterアプリをAndroid実機でビルドする時のセットアップに関しては、こちら。
まとめ:iOSとAndroidの設定の違い
Androidのビルド設定
今まで見てきたように、Androidアプリ開発では、build.gradle
ファイルが中心的な役割を果たす。build.gradle
ファイルはDSL(ドメイン固有言語)を使用しており、開発者はこのスクリプトを通じてアプリのビルドプロセスを細かく制御できる。
iOSのビルド設定
一方、iOS開発環境では、build.gradle
に相当する直接的なファイルは存在しない。
iOSアプリのビルド設定は、主にXcodeのGUIを通じて行われる。Xcodeプロジェクト内の.pbxproj
ファイルにはビルド設定情報が含まれているが、このファイルはXcode独自のフォーマットであり、手動での編集は推奨されていない。
なので、iOSの場合はXcodeのGUIに慣れることを要求され、ビルドプロセスのカスタマイズにおいては、Androidのようなスクリプトベースのアプローチに比べて柔軟性が制限されている。
コメント