Compose Multiplatform 実践:CMPのプロジェクト構造理解とコンパイル設定
Compose Multiplatform (略称CMP)
昨日、CMPのシミュレータ設定を完了したばかりです
前から言い忘れていましたが
実は私はこのシリーズの記事で
初心者
やモバイルアプリ開発
をしていない人でも
入門できるようにしたいと思っています
そのため、冗長に感じる部分や詳しく説明している箇所があります
ご了承くださいXDDD
今日はCMPプロジェクトの構造と
Gradle設定
の調整
そしてCMPプロジェクトにおけるlib.version.toml
の用途について紹介します
目錄
- Compose Multiplatform 實戰:放輕鬆點,初探CMP
- Compose Multiplatform 實戰:初戰,安裝CMP環境吧
- Compose Multiplatform 實戰:續戰,用Wizard創建CMP專案
- Compose Multiplatform 實戰:在Android、iOS模擬器上跑CMP專案
- Compose Multiplatform 實戰:CMP的專案結構理解與編譯配置
- Compose Multiplatform 實戰:CMP中跨平台Android、iOS程式碼的進入點
- Compose Multiplatform 實戰:在CMP的Compose中用Material Design3 Theme
- Compose Multiplatform 實戰:CMP用Compose實作跨平台畫面
- Compose Multiplatform 實戰:使用 expect 和 actual 實現跨平台程式碼
- Compose Multiplatform 實戰:CMP中實作Compose Navigation頁面切換
- Compose Multiplatform 實戰:CMP中透過StateFlow來管理UI狀態
- Compose Multiplatform 實戰:CMP中實作NavigationBar底部欄
- Compose Multiplatform 實戰:CMP中使用koin來依賴注入Dependency Injection
- Compose Multiplatform 實戰:CMP實作跨平台資料庫SqlDelight
- Compose Multiplatform 實戰:CMP中使用ROOM開發跨平台資料庫 & 疑難雜症
これはCMPプロジェクトを作成したときの
デフォルトのプロジェクト構造
です
YourProjectName
├── build
├── composeApp
│ ├── build
│ ├── src
│ │ ├── commonMain
│ │ ├── commonTest
│ │ ├── iosMain
│ │ └── desktopMain
│ └── build.gradle.kts
├── gradle
│ ├── wrapper
│ │ └── libs.versions.toml
├── iosApp
│ ├── iosApp
│ └── iosApp.xcodeproj
├── .gitignore
├── build.gradle.kts
├── gradle.properties
├── local.properties
└── settings.gradle.kts
ここで各フォルダやファイルの用途を整理しました
初心者の方がより早く理解できるよう願っています
YourProjectName
: プロジェクト名であり、プロジェクト全体のrootフォルダですbuild
: コンパイル過程での出力ファイル、これはコンパイルによって生成されるため.gitignore
に追加できますcomposeApp
: Compose Multiplatformアプリケーションのソースコードとその設定を含みますbuild
:CMP
のコンパイル出力、これはコンパイルによって生成されるため.gitignore
に追加できますsrc
:CMP
のプログラムコードディレクトリcommonMain
: CMPプロジェクト共通のロジックコードディレクトリcommonTest
: CMPプロジェクトのテストコードディレクトリiosMain
:iOS
の実装コードディレクトリdesktopMain
:desktop
の実装コードディレクトリ
build.gradle.kts
:CMP
のGradle設定ファイル
gradle
: Gradle関連の設定ファイルwrapper
: Gradle Wrapper関連ファイルを含むlibs.versions.toml
: プロジェクトで使用する依存関係のバージョン
を定義
iosApp
: iOSプロジェクトのrootフォルダiosApp
: iOSのプログラムコードディレクトリ
iosApp.xcodeproj
: iOSのXcodeプロジェクトファイル.gitignore
: Gitバージョン管理で無視すべきファイルやディレクトリを定義build.gradle.kts
:ルートディレクトリ
のGradle設定ファイルgradle.properties
: Gradleプロパティファイルlocal.properties
: ローカル設定のプロパティファイルを定義settings.gradle.kts
:CMP
プロジェクト設定を定義するGradleファイル
libs.versions.toml
はプロジェクトの依存関係バージョンを管理するための設定ファイルです
特にGradleを使用してビルドするプロジェクトにおいて重要です
Gradle公式ドキュメントによると
この機能はGradle 7.0
バージョンでリリースされたものです
また、彼らはこの機能をversion catalogs
と呼んでいます
このファイルはTOML(Tom’s Obvious, Minimal Language)形式を使用して依存関係のバージョン情報を定義し
プロジェクト内でこれらの情報を一元管理することで、保守性と可読性を向上させます
以下はlibs.versions.tomlの例とその説明です:
[versions]
agp = "8.2.0"
kotlin = "2.0.10-RC"
androidx-activityCompose = "1.9.0"
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
主要部分の説明
:
-
バージョン定義
[versions]
:
この部分はプロジェクトで使用される各依存関係のバージョン番号を定義します
例えば、kotlin = “2.0.10-RC”はKotlinのバージョンが2.0.10-RCであることを示します - 依存関係定義
[libraries]
:
この部分はプロジェクトで実際に使用される依存関係とそのバージョン情報を定義します
各ライブラリはmodule
とversion.ref
を定義します
ここでmodule
は依存関係のMaven
であり
version.ref
は上で定義したバージョン番号
を参照します
- 例えば androidx-activity-compose = { module = “androidx.activity:activity-compose”, version.ref = “androidx-activityCompose” }
- は androidx-activityCompose 標準ライブラリのバージョンが androidx-activityCompose で定義されたバージョン番号1.9.0を参照していることを示します
- 例えば androidx-activity-compose = { module = “androidx.activity:activity-compose”, version.ref = “androidx-activityCompose” }
- プラグイン定義
[plugins]
:
この部分はプロジェクトで使用されるプラグインとそのバージョン情報を定義します
各プラグインはid
とversion.ref
を定義します
ここでid
はプラグインの識別子であり
version.ref
も上で定義したバージョン番号を参照します
- 例えば、kotlinMultiplatform = { id = “org.jetbrains.kotlin.multiplatform”, version.ref = “kotlin” }
- はkotlinMultiplatform PluginのバージョンがKotlinで定義されたバージョン番号
2.0.10-RC
を参照していることを示します
- 上記の
.toml
を設定したら
プロジェクトを同期した後
Gradle設定で直接libs.xxx.xx
の形式を使用して依存関係を設定できます
例:
implementation(libs.androidx.activity.compose)
-
この設定方法により、プロジェクトの依存関係バージョン管理がより一元化され、バージョンアップグレードと保守が容易になります。libs.versions.tomlファイルを通じて、プロジェクトは全ての依存関係のバージョン情報を明確に把握でき、複数の場所でバージョン番号を重複定義することによる混乱を避けることができます。
- また、こちらは.tomlにmigrationしてversion catalogsを使用する際に発生する問題について書かれていますので、ご参考ください
build.gradle.kts(:composeApp)
はCMP
プロジェクトを設定するためのGradleビルドスクリプトファイルです
これは`Kotlin DSL(Domain Specific Language)を使用してビルド設定を定義しています<br>
この方法はより強力な
型安全性(Null safety)と
より良いIDE サポート`を提供します
主にアプリのコンパイル時の動作に影響します
しかし、CMP
プロジェクトのbuild.gradle.kts
の内容は長いため
パートに分けて説明します
plugins
ブロック:プラグインをインポートするために使用します
対応するプラグインをインポートするには、lib.version.toml
で宣言されたプラグインを使用します
以下のように:
plugins { alias(libs.plugins.multiplatform) alias(libs.plugins.compose.compiler) alias(libs.plugins.compose) alias(libs.plugins.android.application) alias(libs.plugins.buildConfig) alias(libs.plugins.kotlinx.serialization) }
kotlin
ブロック:
ここでは主にCMP
プロジェクトの設定項目を配置します
例えば、共有ファイル
(String、画像など)の設定
コンパイルJDK
または異なるターゲットプラットフォームの設定など
ここで簡単に説明します:
-
androidTarget > compilerOptions > jvmTarget.set(JvmTarget.JVM_17)
JDK 17を使用してコンパイルするよう設定 -
cocoapods
:Gradle
を通じてcocoapods
をインポートし、iOSのフレームワークを使用 -
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { target -> ...
:
iOS向けの設定、例えばcinterops
を使用してiOSと橋渡しする
CMPが指定されたiOSフレームワークを使用できるようにする -
sourceSets > androidMain.dependencies
とcommonMain.dependencies
など:
ここでは異なるプラットフォーム向けにインポートしたい依存関係を指定できます
例えば:androidMainブロックはandroid
で使用するライブラリをインポート
commonMainブロックは共通ロジック
で使用するライブラリをインポート
さらにiosMainを使用してiOS
で使用するライブラリもインポートできます
ただし、下の例では橋渡し方式を使用しているため
以下を使用しています
listOf(iosX64(), iosArm64(), iosSimulatorArm64())
iOSフレームワークをインポートするための方法ですが
ここでは簡単に触れるだけにします
後の章でiOSの橋渡しについてより詳細に説明します
- 以下は簡単な
build.gradle.kts
の例です
kotlin {
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
cocoapods {
summary = "Bidapp ads for kotlin multiplatform"
version = "1.0"
homepage = "https://github.com/JetBrains/kotlin"
ios.deploymentTarget = "15.4"
podfile = project.file("../iosApp/Podfile")
name = "composeApp"
pod("Google-Mobile-Ads-SDK")
framework {
baseName = "composeApp"
linkerOpts.add("-lsqlite3")
isStatic = true
binaryOption("bundleId", "xxxx.edu")
}
}
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { target ->
...
val frameworkPath = baseDir.resolve(targetArchitecture)
target.compilations.getByName("main") {
cinterops {
create("GoogleMobileAds") {
defFile(project.file("src/nativeInterop/cinterop/GoogleMobileAds.def"))
compilerOpts(
"-framework",
"GoogleMobileAds",
"-F$frameworkPath"
)
}
}
}
target.binaries.all {
linkerOpts(
"-framework",
"GoogleMobileAds",
"-F$frameworkPath"
)
}
}
sourceSets {
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
implementation(libs.google.ads)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.navigation.compose)
implementation(libs.datastore.core)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.androidx.room.runtime)
implementation(libs.sqlite.bundled)
implementation(libs.kotlinx.datetime)
}
}
}
- 可以看到
如果你想要在
共用邏輯導入material3
就直接在commomMain的block中導入即可
不過可能會有人好奇
為啥這邊是compose
.material而不是libs.xxxxxx
這是因為
當你導入KMM插件後
他有內建一下compose專案常用的library
讓你可以直接用
而不用
自己再去lib.version.toml中宣告
commonMain.dependencies {
implementation(compose.material3)
}