Contents
1. 開発環境の要件とインストール手順
| 必須項目 | 推奨バージョン(2026 年 4 月現在) |
|---|---|
| JDK | 17 以上(OpenJDK 17、Oracle JDK 17 等) |
| Android Studio | Flamingo 2026.1(JetBrains が提供する最新安定版) |
| IntelliJ IDEA | 2026 系列(Ultimate または Community) |
| Gradle | 8.x 系列(KMP 用プラグインと互換性あり) |
| Kotlin | 1.9.22(2026‑01 リリース) |
ポイント:上記バージョンは JetBrains が公式にサポートしている最小ラインです。古い IDE や Gradle では KMP プラグインが正しく動作せず、ビルドエラーが頻発します。
1‑1. JDK の導入
- OpenJDK 17(AdoptOpenJDK・Eclipse Temurin 等)または Oracle JDK 17 を公式サイトからダウンロード。
- インストーラ実行後、
java -versionで17.xxxと表示されることを確認。 PATH環境変数に JDK のbinディレクトリが含まれているか最終チェック。
1‑2. Android Studio Flamingo 2026.1 のセットアップ
- JetBrains の公式ダウンロードページから Android Studio Flamingo 2026.1 を取得。
- インストーラに従いインストールし、起動時に「SDK Manager」へ遷移。
- 「SDK Platforms」タブで Android 15(API 35)以上 にチェックを入れ、必要な SDK コンポーネントをダウンロード。
1‑3. IntelliJ IDEA 2026 の導入とプラグイン設定
| 手順 | 操作内容 |
|---|---|
| ① | JetBrains Toolbox App 経由で IntelliJ IDEA 2026 をインストール。 |
| ② | Preferences → Plugins に移動し、Kotlin Multiplatform と Compose Multiplatform が有効か確認。 |
| ③ | File → New → Project で KMP 用テンプレートが表示されることをテスト。 |
2. プロジェクト作成ウィザードと標準ディレクトリ構造
2‑1. 新規 KMP プロジェクトの作成手順
- File → New → Project を選択し、テンプレート一覧から Kotlin Multiplatform をクリック。
- 「Compose Multiplatform」を利用したい場合はチェックを入れる(UI 共有コードが自動生成されます)。
- プロジェクト名・保存先を決めて Finish。
2‑2. 標準的なモジュール構造
|
1 2 3 4 5 6 7 8 9 10 |
<project root> │ ├─ shared ← Gradle のサブプロジェクト(common + platform) │ ├─ src/commonMain ← 全プラットフォームで共有するコード │ ├─ src/androidMain ← Android 固有実装 │ └─ src/iosMain ← iOS (Swift / Obj‑C) 向け実装 │ ├─ androidApp ← Android アプリ側(通常の Android プロジェクト) └─ iosApp ← Xcode で管理する iOS アプリ側 |
- commonMain:データモデル、ビジネスロジック、Compose UI など、全プラットフォームで共通に使用。
- androidMain / iosMain:それぞれの OS が提供する API(例:
Context、NSFileManager)へのブリッジコードを配置。
ポイント:この三層構造は KMP 開発の基盤です。プラットフォーム固有コードと共通ロジックが明確に分離されることで、ビルド時の依存解決が自動化されます。
3. expect / actual と Gradle Kotlin DSL の設定
3‑1. expect / actual の基本概念
| キーワード | 用途 |
|---|---|
| expect | 共通コード側で API シグネチャを宣言。実装は各プラットフォームで提供。 |
| actual | androidMain や iosMain で、expect が要求する具体的な実装を記述。 |
実装例(ログ出力)
|
1 2 3 4 5 |
// commonMain/src/commonMain/kotlin/com/example/platform/Logger.kt package com.example.platform expect fun log(message: String) |
|
1 2 3 4 5 6 7 |
// androidMain/src/androidMain/kotlin/com/example/platform/LoggerAndroid.kt package com.example.platform actual fun log(message: String) { android.util.Log.d("KMP", message) } |
|
1 2 3 4 5 6 7 8 9 |
// iosMain/src/iosMain/kotlin/com/example/platform/LoggerIOS.kt package com.example.platform import platform.Foundation.NSLog actual fun log(message: String) { NSLog("%@", message) } |
3‑2. Gradle Kotlin DSL(build.gradle.kts)の典型的設定
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
plugins { kotlin("multiplatform") version "1.9.22" id("com.android.library") id("org.jetbrains.compose") version "1.5.0" // Compose Multiplatform 用プラグイン } kotlin { androidTarget() iosArm64() iosX64() iosSimulatorArm64() sourceSets { val commonMain by getting { dependencies { implementation(compose.runtime) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") } } val androidMain by getting { dependencies { implementation("androidx.core:core-ktx:1.13.0") implementation(compose.ui) // Android 用 Compose UI } } val iosMain by creating { dependsOn(commonMain) // iOS 向けライブラリがあればここに追加 } } // XCFramework の生成設定(iOS 側で再利用しやすくする) ios { binaries.framework { baseName = "Shared" isStatic = false // 動的フレームワーク (XCFramework) に設定 } } } android { compileSdk = 35 namespace = "com.example.shared" defaultConfig { minSdk = 21 targetSdk = 35 } sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") } |
ポイント:
kotlin("multiplatform")プラグインのバージョンは公式リリースに合わせて固定し、Gradle の8.x系列と組み合わせることでビルドエラーを回避できます。
4. ハンズオン実装例 ― TODO リストアプリ
以下では 共通ロジック + Compose UI を commonMain に集約し、Android と iOS の両方から同一コードを呼び出す手順を示します。
4‑1. データモデルとリポジトリ(commonMain)
|
1 2 3 4 5 6 7 8 9 |
// commonMain/src/commonMain/kotlin/com/example/todo/Task.kt package com.example.todo data class Task( val id: Long, val title: String, var completed: Boolean = false ) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// commonMain/src/commonMain/kotlin/com/example/todo/TodoRepository.kt package com.example.todo import androidx.compose.runtime.mutableStateListOf class TodoRepository { private val tasks = mutableStateListOf<Task>() fun add(title: String) { tasks.add(Task(System.currentTimeMillis(), title)) } fun toggle(id: Long) { tasks.find { it.id == id }?.let { it.completed = !it.completed } } fun getAll(): List<Task> = tasks } |
4‑2. Compose UI(commonMain)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// commonMain/src/commonMain/kotlin/com/example/todo/TodoScreen.kt package com.example.todo import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable fun TodoScreen(repository: TodoRepository) { var newTaskTitle by remember { mutableStateOf("") } val tasks by remember { derivedStateOf { repository.getAll() } } Column( modifier = Modifier.fillMaxSize().padding(16.dp) ) { Row(verticalAlignment = Alignment.CenterVertically) { TextField( value = newTaskTitle, onValueChange = { newTaskTitle = it }, placeholder = { Text("新しいタスク") }, modifier = Modifier.weight(1f) ) Spacer(modifier = Modifier.width(8.dp)) Button(onClick = { if (newTaskTitle.isNotBlank()) { repository.add(newTaskTitle) newTaskTitle = "" } }) { Text("追加") } } Spacer(modifier = Modifier.height(16.dp)) LazyColumn { items(tasks) { task -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) ) { Checkbox( checked = task.completed, onCheckedChange = { repository.toggle(task.id) } ) Text( text = task.title, style = if (task.completed) { LocalTextStyle.current.copy(textDecoration = TextDecoration.LineThrough) } else { LocalTextStyle.current }, modifier = Modifier.padding(start = 8.dp) ) } } } } } |
4‑3. Android エントリポイント(androidMain)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// androidApp/src/main/kotlin/com/example/android/MainActivity.kt package com.example.android import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.example.todo.TodoRepository import com.example.todo.TodoScreen class MainActivity : ComponentActivity() { private val repository = TodoRepository() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { TodoScreen(repository) } } } |
4‑4. iOS エントリポイント(iosMain)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// iosApp/SharedApp/App.swift import SwiftUI import Shared // Kotlin が生成した XCFramework struct ContentView: View { private let repository = TodoRepository() var body: some View { ComposeView { TodoScreen(repository: repository) } .ignoresSafeArea() } } @main struct SharedApp: App { var body: some Scene { WindowGroup { ContentView() } } } |
ポイント:
TodoRepositoryとTodoScreenがcommonMainにあるだけで、Android と iOS の両方から同一ロジックと UI を呼び出せます。これが KMP の最大の魅力です。
5. テスト・デバッグフロー
5‑1. JVM 単体テスト(commonTest)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// commonTest/src/commonTest/kotlin/com/example/todo/TodoRepositoryTest.kt package com.example.todo import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class TodoRepositoryTest { private val repo = TodoRepository() @Test fun add_increasesSize() { repo.add("テストタスク") assertEquals(1, repo.getAll().size) } @Test fun toggle_changesState() { repo.add("切替テスト") val id = repo.getAll().first().id repo.toggle(id) assertTrue(repo.getAll().first().completed) } } |
実行コマンド:
|
1 2 |
./gradlew clean testDebugUnitTest |
5‑2. Android デバッグ手順
- Run ボタンでエミュレータ(例:Pixel 7 API 35)を起動。
LogcatにKMPタグやprintlnが出力されていれば、共有コードが正しく走っています。
5‑3. iOS Simulator デバッグ手順
- Xcode(15.4 以上)で iosApp プロジェクトを開く。
Cmd + Rで iPhone 15 シミュレータを起動。- コンソールに Kotlin の
printlnと Swift のprintが混在して表示されることを確認。
5‑4. ビルドエラー対策(よくあるケース)
| エラーメッセージ | 主な原因 | 解決策 |
|---|---|---|
Could not resolve all files for configuration ':iosX64MainRuntimeElements' |
Xcode Command Line Tools が未インストール | ターミナルで xcode-select --install を実行 |
Unsupported Kotlin version |
Gradle で参照している Kotlin バージョンが古い | build.gradle.kts の kotlin("multiplatform") バージョンを 1.9.22 に固定 |
Compose resources not found(iOS) |
composeResources DSL が未設定 |
compose { resources { packageName = "com.example.shared" } } を commonMain の Gradle 設定に追記 |
ポイント:公式 codelab や JetBrains のリリースノートは随時更新されます。特に 2025‑2026 年の大幅アップデート情報は必ずチェックしてください。
6. サンプルプロジェクトの取得とビルド
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 作業ディレクトリへ移動 cd ~/Development # GitHub の公式サンプルをクローン(URL は執筆時点のもの) git clone https://github.com/JetBrains/kotlin-multiplatform-samples.git cd kotlin-multiplatform-samples/todo-app # 必要な依存関係を取得し、Android と iOS のビルドを確認 ./gradlew :androidApp:assembleDebug # Android デバッグ APK を生成 xcodebuild -scheme iosApp \ -destination 'platform=iOS Simulator,name=iPhone 15' \ clean build # iOS シミュレータ用フレームワークをビルド |
クローンしたリポジトリは README に沿って Android Studio と Xcode の両方で開くことができます。実行確認が取れたら、自分のアプリケーションに合わせてコードを拡張してみましょう。
7. まとめ
| 項目 | 内容 |
|---|---|
| 開発環境 | JDK 17+、Android Studio Flamingo 2026.1、IntelliJ IDEA 2026、Gradle 8.x、Kotlin 1.9.22 |
| プロジェクト構造 | commonMain / androidMain / iosMain の三層でコードを分離 |
期待/実装 (expect/actual) |
プラットフォーム固有 API を安全に呼び出すための必須テクニック |
| Gradle 設定 | kotlin("multiplatform") と compose プラグインで XCFramework 生成を構成 |
| ハンズオン例 | TODO アプリで共通ロジック・Compose UI を commonMain に実装し、Android/iOS から呼び出すだけのシンプル構成 |
| テスト & デバッグ | JVM 単体テスト → Android エミュレータ → iOS Simulator の順に検証 |
| ビルドエラー対策 | Xcode CLI ツール、Kotlin バージョン固定、codelab 更新情報の確認が鍵 |
| サンプル取得 | 公式 GitHub リポジトリからクローンし、./gradlew assembleDebug と xcodebuild でビルド |
この手順に沿って環境を整え、サンプルプロジェクトを動かすことで、2026 年版のツールチェーン下で Kotlin Multiplatform の基本的な流れとベストプラクティスを体感できます。次は実際のビジネスロジックや UI デザインに合わせてモジュールを拡張し、真のクロスプラットフォーム開発へとステップアップしてください。