Contents
開発環境のセットアップ
Kotlin Multiplatform (KMP) アプリを作り始める前に、JDK・Android Studio・Xcode の最新版 と Kotlin 用プラグインが正しくインストールされていることを確認します。ここで環境が整っていれば、ビルドエラーや依存関係の不整合といった基本的な障害に悩まされる機会は格段に減ります。
JDK と Android Studio のインストール
対象 OS:macOS・Windows・Linux(それぞれの手順を併記)
- JDK 17 以上 が Kotlin 2.0 系列で必須です。
- macOS:
brew install openjdk@17→ インストール後、/usr/local/opt/openjdk@17/binを$PATHに追加します。 - Windows: Microsoft Store か AdoptOpenJDK の公式ページから MSI を取得し、インストーラに従ってインストールしてください。
-
Linux (Ubuntu 系):
sudo apt-get install openjdk-17-jdk -
Android Studio(最新安定版)
- 公式サイトの「Download Android Studio」から Android Studio Flamingo 系列以降をダウンロードします(2026 年 4 月時点での最新版は
Flamingo (2024.2.x))。 - 初回起動後に表示される SDK Manager で次のコンポーネントを必ずインストールしてください。
- Android 15(API 35)またはそれ以上のプラットフォーム
- Google Play services、NDK、CMake(必要に応じて)
-
File > Settings > Appearance & Behavior > System Settings > Android SDKで SDK Path が正しく設定されていることを確認します。 -
環境変数のチェック(ターミナル/コマンドプロンプト)
|
1 2 3 4 5 6 |
# JDK バージョン確認 java -version # → openjdk version "17..." # Android Studio バージョン確認 (macOS の例) studio.sh --version # → Android Studio Flamingo 2024.2.x |
注意:JDK と Android Studio が同一メジャーバージョンで揃っていないと、Kotlin コンパイラが期待通りに動作しないことがあります。
iOS 開発環境の準備(Xcode)
iOS アプリをビルドするには macOS が必須です。Apple が提供する 最新安定版 Xcode(2026 年 4 月時点では 15.4 系列)が推奨されますが、正確なバージョン番号は公式リリースノートで随時確認してください。
- App Store または Apple Developer ポータルから Xcode をインストール/アップデートします。
- コマンドラインツールを有効化するために以下を実行します。
|
1 2 |
xcode-select --install # 初回のみ表示されるダイアログで「インストール」を選択 |
- Command Line Tools が正しい Xcode バージョンに紐付いているか確認します。
|
1 2 |
xcode-select -p # → /Applications/Xcode.app/Contents/Developer になるはず |
- シミュレータは Xcode の「Devices & Simulators」から iPhone 15、iPad Pro 等の最新デバイスイメージを追加してください。
注意:シミュレータが起動しない場合は
xcrun simctl listでインストール済みランタイムを確認し、必要に応じてxcode-select --switch /Applications/Xcode.appを実行します。
Kotlin プラグインと関連ツールの導入
Kotlin 2.0 系列と Compose Multiplatform の機能をフル活用するために、IDE 側へ以下プラグインを追加します(バージョンは「最新安定版」を使用)。
| ツール | 推奨設定 | インストール手順 |
|---|---|---|
| Kotlin Plugin for Android Studio | 最新安定版 (例: 2.0.x) | Preferences → Plugins → Marketplace → 「Kotlin」検索・インストール |
| Compose Multiplatform Plugin | 最新安定版 (例: 1.5.x) | 同上で「Compose Multiplatform」検索 |
| Kotlin Symbol Processing (KSP) | 最新安定版 (例: 2.0.x‑rc) | build.gradle.kts に id("com.google.devtools.ksp") version "…" を記述 |
ポイント:プラグインは自動更新が無効化されていることが多いため、月に一度「Check for Updates」からバージョンを確認しましょう。
KMP プロジェクトの作成と Gradle 設定
Gradle Kotlin DSL と Version Catalog を組み合わせることで、依存バージョン管理が一元化され、プラットフォームごとのビルドスクリプトがシンプルになります。以下では標準的なディレクトリ構成と設定ファイル例を示します。
プロジェクト雛形の生成
前提:プロジェクトのルートに
gradlew(Gradle Wrapper)があることを想定しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 1. 作業ディレクトリ作成 mkdir MyKmpApp && cd MyKmpApp # 2. Gradle Wrapper の初期化(まだ無い場合) gradle wrapper --gradle-version 8.6 # 3. Kotlin Multiplatform 雛形を生成 ./gradlew init \ --type kotlin-multiplatform \ --dsl kotlin \ --project-name MyKmpApp |
このコマンドで以下の主要ディレクトリが作成されます。
commonMain/,androidMain/,iosX64/などの source setbuild.gradle.kts(ルートビルドスクリプト)settings.gradle.kts
settings.gradle.kts に Version Catalog を有効化
|
1 2 3 4 5 6 7 8 9 |
dependencyResolutionManagement { versionCatalogs { create("libs") { // カタログは libs.versions.toml で管理するのが推奨です。 from(files("../gradle/libs.versions.toml")) } } } |
libs.versions.toml(例)
gradle/libs.versions.toml をプロジェクト直下に作成し、バージョン・プラグイン・ライブラリをすべてここで定義します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[versions] kotlin = "2.0.20" compose = "1.5.10" ktor = "3.0.0" sqldelight = "2.0.0" atomicfu = "0.23.1" [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } compose = { id = "org.jetbrains.compose", version.ref = "compose" } sqldelight = { id = "com.squareup.sqldelight", version.ref = "sqldelight" } ksp = { id = "com.google.devtools.ksp", version = "2.0.20-rc" } [libraries] ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-android = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-ios = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } sqldelight-runtime = { module = "com.squareup.sqldelight:runtime", version.ref = "sqldelight" } sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-native = { module = "com.squareup.sqldelight:native-driver", version.ref = "sqldelight" } atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" } |
ポイント:
libs.versions.tomlはプロジェクト全体で共有できるため、サブモジュールが増えてもバージョン管理が煩雑になりません。
Kotlin Multiplatform と Compose のプラグイン設定
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 53 54 55 56 57 |
plugins { // カタログからプラグインを取得 alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.compose) alias(libs.plugins.sqldelight) alias(libs.plugins.ksp) // KSP 用 } kotlin { androidTarget() iosX64() iosArm64() iosSimulatorArm64() sourceSets { val commonMain by getting { dependencies { implementation(compose.runtime) implementation(libs.ktor.core) implementation(libs.sqldelight.runtime) implementation(libs.atomicfu) // @SharedImmutable 用 } } val androidMain by getting { dependencies { implementation(libs.ktor.android) implementation(libs.sqldelight.driver.android) } } val iosMain by creating { dependsOn(commonMain) dependencies { implementation(libs.ktor.ios) implementation(libs.sqldelight.driver.native) } } // テスト用 sourceSet val commonTest by getting { dependencies { implementation(kotlin("test")) implementation(libs.kotlinx.coroutines.test) } } } } // Android 用設定(必要最低限) android { compileSdk = 35 namespace = "com.example.mykmpapp" defaultConfig { minSdk = 24 targetSdk = 35 } } |
注意:
libs.versions.tomlに定義したエイリアス(alias(libs.plugins.xxx))は Gradle 8.x 系で正式にサポートされています。古い Gradle を使用している場合はid("…") version "…"の形式に書き換えてください。
Version Catalog の活用例とベストプラクティス
| カテゴリ | 設定例 | 利用シーン |
|---|---|---|
| バージョン | version("kotlin", "2.0.20") |
複数モジュールで同一バージョンを共有 |
| プラグイン | plugin("compose", "org.jetbrains.compose").versionRef("compose") |
Gradle スクリプトの可読性向上 |
| ライブラリ | library("ktor-core", "io.ktor:ktor-client-core").versionRef("ktor") |
implementation(libs.ktor.core) で呼び出し |
実装手順まとめ
- libs.versions.toml を作成(上記例をベースにプロジェクト固有の依存を追加)。
settings.gradle.ktsでカタログを読み込む。- ビルドスクリプトでは
libs.xxx系統だけを書き、バージョン番号は一切ハードコーディングしない。
この流れに従えば、将来のバージョンアップは toml ファイル のみ修正すれば完了します。
共通コード層の設計とマルチプラットフォームライブラリ活用
KMP で最大限コード共有を実現する鍵は Expect/Actual パターン と、Ktor・SQLDelight 等のマルチプラットフォーム対応ライブラリです。以下では具体的な実装例とベストプラクティスを示します。
Expect / Actual によるプラットフォーム差分抽象化
概要:共通モジュール (
commonMain) でexpectキーワードで宣言し、各ターゲット固有の実装はactualキーワードで提供します。
|
1 2 3 4 5 6 |
// commonMain/src/commonMain/kotlin/com/example/util/TimeProvider.kt package com.example.util /** 現在時刻(ミリ秒)を取得する抽象インタフェース */ expect fun currentTimeMillis(): Long |
Android 実装
|
1 2 3 4 5 |
// androidMain/src/androidMain/kotlin/com/example/util/TimeProvider.android.kt package com.example.util actual fun currentTimeMillis(): Long = System.currentTimeMillis() |
iOS (Kotlin/Native) 実装
|
1 2 3 4 5 6 7 8 |
// iosMain/src/iosMain/kotlin/com/example/util/TimeProvider.ios.kt package com.example.util import platform.Foundation.NSDate actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong() |
ポイント:
expect / actualはコンパイル時に型安全が保証され、IDE がプラットフォーム固有実装へのナビゲーションを提供します。
Ktor によるネットワーク層の共有
Ktor のマルチプラットフォームクライアントは エンジン を差し替えるだけで Android と iOS 両方で同一コードが動作します。
共通モジュール
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// commonMain/src/commonMain/kotlin/com/example/network/ApiClient.kt package com.example.network import io.ktor.client.* import io.ktor.client.request.* private val httpClient = HttpClient { install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) { json() } } /** サンプル API 呼び出し */ suspend fun fetchPosts(): List<Post> = httpClient.get("https://jsonplaceholder.typicode.com/posts") |
プラットフォーム別エンジン設定(Expect/Actual パターン)
|
1 2 3 4 5 6 7 |
// commonMain/src/commonMain/kotlin/com/example/network/EngineProvider.kt package com.example.network import io.ktor.client.engine.* expect fun provideEngine(): HttpClientEngine |
|
1 2 3 4 5 6 7 8 9 10 |
// androidMain/src/androidMain/kotlin/com/example/network/EngineProvider.android.kt package com.example.network import io.ktor.client.engine.okhttp.* import okhttp3.OkHttpClient actual fun provideEngine(): HttpClientEngine = OkHttpEngine( client = OkHttpClient.Builder().build() ) |
|
1 2 3 4 5 6 7 |
// iosMain/src/iosMain/kotlin/com/example/network/EngineProvider.ios.kt package com.example.network import io.ktor.client.engine.darwin.* actual fun provideEngine(): HttpClientEngine = DarwinEngine() |
HttpClient の生成箇所を provideEngine() に置き換えるだけで、ビルドフラグや Gradle プロパティでエンジン切替が可能です。
|
1 2 3 4 |
private val httpClient = HttpClient(provideEngine()) { // 共通設定はここに書く } |
ポイント:テスト時には
-PuseMockServer=trueのような Gradle プロパティを用意し、provideEngine()が Mock エンジンを返すように切り替えることで、ネットワーク依存のユニットテストが容易になります。
SQLDelight によるデータベース層の統一
SQLDelight はスキーマから型安全な DAO を自動生成し、JVM と Native の両方で同一 API を提供します。
Gradle 設定(build.gradle.kts)
|
1 2 3 4 5 6 7 |
sqldelight { database("AppDatabase") { packageName = "com.example.shared.db" sourceFolders = listOf("sqldelight") } } |
スキーマは src/commonMain/sqldelight/com/example/shared/db/App.sq に配置します。
|
1 2 3 4 5 6 |
CREATE TABLE user ( id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, age INTEGER NOT NULL ); |
ドライバ抽象化(Expect/Actual)
|
1 2 3 4 5 6 7 8 9 |
// commonMain/src/commonMain/kotlin/com/example/db/DriverFactory.kt package com.example.db import com.squareup.sqldelight.db.SqlDriver expect class DriverFactory { fun createDriver(): SqlDriver } |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// androidMain/.../DriverFactory.android.kt package com.example.db import android.content.Context import com.squareup.sqldelight.android.AndroidSqliteDriver import com.example.shared.db.AppDatabase actual class DriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver = AndroidSqliteDriver(AppDatabase.Schema, context, "app.db") } |
|
1 2 3 4 5 6 7 8 9 10 11 |
// iosMain/.../DriverFactory.ios.kt package com.example.db import com.squareup.sqldelight.drivers.native.NativeSqliteDriver import com.example.shared.db.AppDatabase actual class DriverFactory { actual fun createDriver(): SqlDriver = NativeSqliteDriver(AppDatabase.Schema, "app.db") } |
DAO の利用例(commonMain)
|
1 2 3 4 5 6 |
val db = AppDatabase(driverFactory.createDriver()) val userQueries = db.userQueries suspend fun getUser(id: Long): User? = userQueries.selectById(id).executeAsOneOrNull() |
ポイント:iOS ビルド時は
NativeSqliteDriverが自動的に使用され、SQLite の挙動は Android と完全に同一です。
Kotlin 2.0 で導入された @SharedImmutable は コンパイル時最適化 により、マルチスレッド環境でもロック不要な不変オブジェクトを生成します。利用には atomicfu が必須です。
依存追加(libs.versions.toml)
|
1 2 3 |
[libraries] atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" } |
コード例
|
1 2 3 4 5 6 7 8 |
import kotlinx.atomicfu.SharedImmutable @SharedImmutable object Config { const val TIMEOUT_MS = 15_000L const val BASE_URL = "https://api.example.com" } |
ポイント:
@SharedImmutableを付与したobjectやvalはコンパイル時に 静的バイトコード に変換され、実行時のオーバーヘッドがほぼゼロになります。頻繁に参照する定数は必ずこのアノテーションでマークしましょう。
UI 実装の選択肢と比較ガイド
KMP アプリでは Compose Multiplatform と SwiftUI のどちらか、またはハイブリッド構成を取ることが多いです。以下ではそれぞれの特徴と実装サンプルを示し、プロジェクト要件に合わせた選択指針を提供します。
Compose Multiplatform で統一的に UI を構築
概要:単一コードベース(Kotlin)で Android と iOS の UI を描画でき、プレビュー機能が IDE に組み込まれています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Composable fun GreetingScreen(name: String) { Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Hello, $name!", style = MaterialTheme.typography.h4) Spacer(modifier = Modifier.height(12.dp)) Button(onClick = { /* TODO */ }) { Text("Tap me") } } } |
プレビューの書き方(iOS も含めて)
|
1 2 3 4 5 6 |
@Preview(name = "iPhone 15", device = "iPhone 15") @Composable fun GreetingScreenPreview() { GreetingScreen(name = "KMP") } |
- テーマ統一:
MaterialThemeをベースにColorSchemeをカスタマイズすれば、iOS でも Material ライクな見た目を実現可能です。 - プラットフォーム固有の UI 部分だけ切り替える:
expect fun isIOS(): Booleanとactual実装で条件分岐できます。
SwiftUI と KMP のブリッジパターン
概要:iOS 側は Apple が推奨する SwiftUI で UI を実装し、ビジネスロジックだけを Kotlin/Native から呼び出す構成です。
Swift 側コード例
|
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 |
import SwiftUI import shared // Gradle が生成した Kotlin/Native フレームワーク struct ContentView: View { @State private var greeting = "" var body: some View { VStack(spacing: 16) { Text(greeting) .font(.title) Button("Fetch") { fetchGreeting() } } .onAppear { fetchGreeting() } } private func fetchGreeting() { let useCase = GreetingUseCase() // Kotlin の suspend 関数は Swift では async/await に変換されます Task { do { greeting = try await useCase.getGreeting() } catch { greeting = "Error: \\(error.localizedDescription)" } } } } |
KMP 側の GreetingUseCase(共通コード)
|
1 2 3 4 5 |
class GreetingUseCase { suspend fun getGreeting(): String = httpClient.get("https://api.example.com/greeting") } |
- ブリッジポイントは
shared.frameworkのビルド設定です。Gradle のlinkReleaseFrameworkIosX64タスクで生成された.frameworkを Xcode にインポートすれば、Swift から直接呼び出せます。
フレームワーク選択の判断基準
| 観点 | Compose Multiplatform | SwiftUI |
|---|---|---|
| コード統一性 | 高(Kotlin のみ) | 中(ロジックは共有、UI は別) |
| プラットフォームネイティブ感 | 良好だが Material デザインに依存 | 最高(Apple HIG 完全遵守) |
| 開発速度 | UI プレビューで高速 | Xcode の Live Preview が同等 |
| チームスキル | Kotlin エンジニア中心 | iOS/Swift エンジニア必須 |
| 将来拡張性 | Web・Desktop も同一コードで追加可能 | iOS/macOS に限定 |
結論:Kotlin エンジニアが多数在籍し、Android と iOS の UI をできるだけ共通化したい場合は Compose Multiplatform が最適です。Apple デザインの徹底遵守と高度な iOS ネイティブ体験が必要なら SwiftUI + KMP ブリッジ が有効です。
テスト・デバッグ・ビルド・リリースフロー
KMP プロジェクトでは 共通ユニットテスト と プラットフォーム固有テスト を組み合わせ、CI/CD で自動化することが品質保証の鍵です。
ユニットテストとプラットフォーム固有テスト
共通モジュール (commonTest) の例
|
1 2 3 4 5 6 7 8 9 10 |
class GreetingUseCaseTest { private val useCase = GreetingUseCase() @Test fun `greeting is not empty`() = runBlocking { val result = useCase.getGreeting() assertTrue(result.isNotBlank()) } } |
Android Instrumented Test
|
1 2 3 4 5 6 7 8 |
@get:Rule val composeTestRule = createComposeRule() @Test fun greetingScreen_showsText() { composeTestRule.setContent { GreetingScreen(name = "KMP") } composeTestRule.onNodeWithText("Hello, KMP!").assertIsDisplayed() } |
androidTest の build.gradle.kts で必要な依存を追加します。
|
1 2 3 4 5 |
dependencies { androidTestImplementation(compose.ui.test.junit4) androidTestImplementation(kotlin("test-junit")) } |
iOS XCTest(Swift 側)
|
1 2 3 4 5 6 7 8 9 10 11 |
import XCTest @testable import shared final class GreetingUseCaseTests: XCTestCase { func testGreetingNotEmpty() async throws { let useCase = GreetingUseCase() let greeting = try await useCase.getGreeting() XCTAssertFalse(greeting.isEmpty) } } |
ポイント:
./gradlew allTestsでcommonTest・androidTest・iosX64Test·iosArm64Testが一括実行でき、ローカルでも CI でも同じコマンドを使用できます。
完全版 GitHub Actions パイプライン
以下は Android と iOS のビルド・テスト・デプロイまでを網羅したサンプルです。コメントで省略部分を補足しています。
|
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
name: KMP CI/CD on: push: branches: [ main ] pull_request: jobs: # ------------------------------------------------- # 共通ビルド環境(macOS が iOS ビルドに必須) # ------------------------------------------------- setup: runs-on: macos-13 outputs: java-version: ${{ steps.java.outputs.version }} steps: - uses: actions/checkout@v3 # JDK 17 のセットアップ(macOS, Linux 共通) - name: Set up JDK 17 id: java uses: actions/setup-java@v3 with: distribution: temurin java-version: '17' # Android SDK のインストール - name: Install Android SDK uses: android-actions/setup-android@v2 # Gradle キャッシュ設定 - name: Cache Gradle packages uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} restore-keys: | ${{ runner.os }}-gradle- # ------------------------------------------------- # テスト実行ジョブ(Android + iOS 共通) # ------------------------------------------------- test: needs: setup runs-on: macos-13 steps: - uses: actions/checkout@v3 # JDK と Android SDK は前の job で設定済みなので再利用 - name: Restore Gradle cache uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} # すべてのテストを実行 - name: Run all unit & instrumented tests run: ./gradlew clean testDebugUnitTest assembleDebug # iOS の Kotlin/Native テスト - name: Run iOS (Simulator) tests run: | xcodebuild -scheme MyKmpApp \ -destination 'platform=iOS Simulator,name=iPhone 15,OS=latest' \ test # ------------------------------------------------- # ビルド & アーティファクト生成ジョブ # ------------------------------------------------- build: needs: test runs-on: macos-13 steps: - uses: actions/checkout@v3 - name: Build Android AAB run: ./gradlew :app:bundleRelease - name: Build iOS XCFramework run: | ./gradlew :shared:linkReleaseFrameworkIosArm64 \ :shared:linkReleaseFrameworkIosX64 # 生成された .framework を zip にまとめる cd shared/build/bin/iosArm64/releaseFramework/ zip -r MyKmpApp.xcframework.zip *.xcframework # アーティファクトを保存(GitHub の UI からダウンロード可能に) - name: Upload AAB artifact uses: actions/upload-artifact@v3 with: name: android-aab path: app/build/outputs/bundle/release/app-release.aab - name: Upload XCFramework artifact uses: actions/upload-artifact@v3 with: name: ios-xcframework path: shared/build/bin/**/*.zip # ------------------------------------------------- # デプロイジョブ(Google Play & App Store Connect) # ------------------------------------------------- deploy: needs: build runs-on: macos-13 steps: - uses: actions/checkout@v3 # ダウンロードしたビルド成果物を取得 - name: Download AAB uses: actions/download-artifact@v3 with: name: android-aab path: ./artifacts - name: Download XCFramework uses: actions/download-artifact@v3 with: name: ios-xcframework path: ./artifacts # ---------- Google Play ---------- - name: Deploy to Google Play (internal track) if: github.ref == 'refs/heads/main' uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }} packageName: com.example.mykmpapp releaseFiles: ./artifacts/app-release.aab track: internal # ---------- App Store Connect ---------- - name: Convert XCFramework to .ipa (Xcode) run: | xcodebuild -create-xcframework \ -framework ./artifacts/MyKmpApp.xcframework.zip \ -output MyKmpApp.xcframework # 簡易的に ipa へラップ(実際は Xcode Archive が必要) xcrun altool --upload-app -f MyKmpApp.xcframework -t ios - name: Upload to App Store Connect uses: appleboy/app-store-connect@v2 with: api_key_id: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} issuer_id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} private_key: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }} ipa_path: MyKmpApp.xcframework # 実際は .ipa が必要 |
ポイント解説
| ステップ | 目的・補足 |
|---|---|
setup |
macOS ランナーで JDK と Android SDK を一度だけインストールし、キャッシュを共有。 |
test |
./gradlew testDebugUnitTest で共通ユニットテスト、xcodebuild test で iOS シミュレータ上の XCTest を実行。 |
build |
Android AAB と iOS XCFramework(または .ipa)を生成し、後続ジョブへアーティファクトとして渡す。 |
deploy |
Google Play の internal トラックと App Store Connect に自動アップロード。シークレット管理は必須。 |
デバッグ・プロファイリングの実践
- KSP 生成コードの確認
- Android Studio:
Build > Show Generated Sources...→build/generated/ksp/...を開く。 -
macOS ターミナルで iOS の場合は
./gradlew :shared:compileKotlinIosArm64 --infoとすれば生成ファイルのパスが出力されます。 -
CPU / メモリ プロファイラ
- Android Studio Profiler → アプリ起動中に CPU、Memory、Network タブでリアルタイム計測。KSP が生成したコードは通常のクラスと同様にプロファイル対象です。
-
Xcode Instruments →
AllocationsとTime Profilerを使い、MyKmpApp.framework内のシンボルを追跡します。 -
ログ出力とデバッグブレークポイント
- Kotlin/Native では
println()が標準出力に流れますが、Xcode のコンソールでも確認可能です。 - Android Studio と Xcode 両方で同一ブレークポイントを設定できるように、コードは commonMain に置き、
expect/actualでプラットフォーム差分だけ抽象化します。
注意:KSP はビルド時のみ実行されるため、ランタイムオーバーヘッドはありませんが、生成コードが大規模になると デバッグシンボルサイズ が増える点に留意してください。
まとめ
- 開発環境は JDK 17 以上、Android Studio の最新安定版、macOS 上の最新版 Xcode を揃え、Kotlin プラグインと KSP を導入すればトラブルが激減します。
- Gradle Kotlin DSL と Version Catalog による依存管理はバージョンアップを一箇所で完結させ、プロジェクト規模が拡大しても保守コストを抑えられます。
- Expect/Actual パターンとマルチプラットフォーム対応ライブラリ(Ktor・SQLDelight・atomicfu)を組み合わせることで、ビジネスロジックは完全に共有可能です。
- UI は Compose Multiplatform と SwiftUI のハイブリッド から選択し、プロジェクト要件とチームの技術スタックに合わせて最適なアーキテクチャを決定してください。
- テスト戦略は共通ユニットテスト+プラットフォーム固有テストで網羅的にカバーし、GitHub Actions の完全サンプルワークフローを活用すれば CI/CD が自動化されます。
- デバッグ・プロファイリングは Android Studio と Xcode の標準ツールで十分対応でき、KSP 生成コードの確認手順も明示しておくと安心です。
これらの手順とベストプラクティスを踏襲すれば、Kotlin Multiplatform アプリ開発が 高速・安全・拡張性の高い プロセスへと変貌し、チーム全体の生産性向上に直結します。ぜひ本ガイドをプロジェクトの立ち上げ時に活用してください。