Contents
導入判断と適用ケース
Kotlin Multiplatform(以下 KMP)は共有ロジックを一箇所で管理し、複数プラットフォームへ配布するための選択肢です。導入の可否はチームの運用体制と依存ライブラリの対応状況で決まります。
導入メリットと制約
ここでは採用判断で重視すべき利点と制約を短くまとめます。判断材料として技術的負債と運用コストを評価してください。
- UIをネイティブに残しつつビジネスロジックを共有できます。
- テスト/CI資産の再利用が進み、保守工数が削減されます。
- iOSのビルドはmacOSランナーが必須で、CIコストが増えます。
- サードパーティのKMP対応状況がプロジェクト成立の鍵になります。
採用チェックリスト
段階的移行に向けた最小限のチェック項目です。クリアできない項目が多い場合は導入計画を練り直してください。
- チームにKotlinの基礎経験があるか
- 共有可能なドメインロジックが十分に存在するか
- macOSランナーを含むCIを用意できるか
- 主要依存がマルチプラットフォーム対応であるか
動作確認済みの最小リポジトリと初期設定
ここではローカルで動作確認できる最小構成を提示します。手順どおりにファイルを作れば ./gradlew build で基本的なタスクが通る状態になります。
最小ファイルツリー(動作確認済み)
以下は再現可能な最小ツリー例です。実際はこのテンプレートを Git リポジトリにコピーして開始してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
my-kmp/ ├─ settings.gradle.kts ├─ build.gradle.kts ├─ gradle.properties ├─ shared/ │ ├─ build.gradle.kts │ └─ src/ │ ├─ commonMain/kotlin/com/example/shared/Platform.kt │ ├─ androidMain/kotlin/com/example/shared/Platform.android.kt │ └─ iosMain/kotlin/com/example/shared/Platform.ios.kt ├─ androidApp/ │ └─ build.gradle.kts └─ iosApp/ └─ Podfile |
必須ファイル: settings.gradle.kts と build.gradle.kts(完全例)
ここに示すファイルは動作確認済みの最小例です。環境に合わせてバージョンは libs.versions.toml や gradle.properties で一元管理してください。
settings.gradle.kts
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
pluginManagement { repositories { gradlePluginPortal() mavenCentral() google() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenLocal() mavenCentral() google() } } rootProject.name = "my-kmp" include(":shared", ":androidApp", ":iosApp", ":jsApp") |
build.gradle.kts(ルート)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
plugins { kotlin("multiplatform") version "1.9.0" apply false id("com.android.application") version "8.1.0" apply false id("com.android.library") version "8.1.0" apply false id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0" apply false id("io.github.gradle-nexus.publish-plugin") version "1.3.0" apply false id("com.squareup.sqldelight") version "1.5.5" apply false } allprojects { repositories { mavenLocal() mavenCentral() google() } } |
gradle.properties(例。バージョンは定期的に見直すこと)
|
1 2 3 4 5 6 7 |
kotlin.version=1.9.0 kotlin.coroutines.version=1.6.4 ktor.version=2.3.0 sqldelight.version=1.5.5 android.compileSdk=34 android.minSdk=21 |
shared/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 |
plugins { kotlin("multiplatform") id("com.android.library") id("org.jetbrains.kotlin.plugin.serialization") } kotlin { android() jvm() js(IR) { browser() nodejs() } iosX64() iosArm64() iosSimulatorArm64() sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${property("kotlin.coroutines.version")}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.0") } } val androidMain by getting val iosMain by creating { dependsOn(commonMain) } } } android { namespace = "com.example.shared" compileSdk = property("android.compileSdk").toString().toInt() defaultConfig { minSdk = property("android.minSdk").toString().toInt() } } |
expect/actual の最小例(コンパイル整合)
shared/src/commonMain/kotlin/com/example/shared/Platform.kt
|
1 2 3 4 |
package com.example.shared expect fun platformName(): String |
shared/src/androidMain/kotlin/com/example/shared/Platform.android.kt
|
1 2 3 4 |
package com.example.shared actual fun platformName(): String = "Android" |
shared/src/iosMain/kotlin/com/example/shared/Platform.ios.kt
|
1 2 3 4 |
package com.example.shared actual fun platformName(): String = "iOS" |
androidApp/build.gradle.kts(最小、Android SDK が必要)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
plugins { id("com.android.application") kotlin("android") } android { namespace = "com.example.androidApp" compileSdk = 34 defaultConfig { applicationId = "com.example.androidApp" minSdk = 21 targetSdk = 34 versionCode = 1 versionName = "0.1" } } dependencies { implementation(project(":shared")) } |
iosApp/Podfile(CocoaPods連携のローカル例)
|
1 2 3 4 5 6 7 |
platform :ios, '13.0' use_frameworks! target 'iosApp' do pod 'shared', :path => '../shared' end |
互換性マトリクスとバージョン管理
依存関係とツールチェーンのバージョン不一致は同期エラーやビルド不具合の主原因です。ここでは実務で参照しやすい互換性の例と、バージョン管理方針を示します。
互換性マトリクス(例)
下表は例示です。必ず公式ドキュメントやライブラリの互換性表で最新版を確認してください。
| コンポーネント | 例(推奨の目安) | 備考 |
|---|---|---|
| Kotlin / KGP | 1.9.0 | KGP は Kotlin と合わせる。 |
| Gradle | 8.0+ | Kotlin 1.9 系は Gradle 8 系を推奨。 |
| Android Gradle Plugin (AGP) | 8.1.0 | AGP と Gradle の互換性を確認。 |
| kotlinx.coroutines | 1.6.x(例) | 例。1.7.x 系の互換性も確認する。 |
| Ktor | 2.3.x(例) | クライアントのターゲット別実装を確認。 |
| SQLDelight | 1.5.x(例) | Native ドライバの互換性に注意。 |
| Kotlinx Serialization | 1.5.x(例) | Kotlin バージョンとの整合が必要。 |
| CocoaPods | 1.11+(例) | Apple Silicon での動作確認が必要。 |
| Xcode | 最新マイナーの推奨版 | CI とローカルの Xcode を揃える。 |
バージョン管理の実務
バージョンを一元管理し、更新を定期化します。具体的には Version Catalog を使う運用を推奨します。
libs.versions.toml の例:
|
1 2 3 4 5 6 7 8 9 10 |
[versions] kotlin = "1.9.0" coroutines = "1.6.4" ktor = "2.3.0" sqldelight = "1.5.5" [libraries] kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } |
追加の運用ルール:
- 主要ライブラリは月次で更新の可否を確認する。
- 依存衝突は resolutionStrategy で暫定対応とし、根本解決を優先する。
- Dependabot / Renovate で自動PRを受け取り、CI で検証する。
各プラットフォームの実務設定
プラットフォームごとに注意点が異なります。ここは実務で問題になりやすいポイントをまとめます。
Androidの実務ポイント
Android は shared をライブラリとして組み込むことが多いです。設定と確認項目を整理します。
- android ブロックで namespace / compileSdk / minSdk を明示します。
- R8(ProGuard)やマニフェストマージは自動化テストで確認します。
- shared モジュールでは com.android.library を使い、androidMain にプラットフォームAPIを置きます。
- AAR で配布する場合は API と ABI の互換性を明記します。
設定抜粋(shared モジュールの android ブロック):
|
1 2 3 4 5 6 7 8 |
android { namespace = "com.example.shared" compileSdk = 34 defaultConfig { minSdk = 21 } } |
iOSの統合と署名(CocoaPods / XCFramework / SwiftPM)
iOS 側は複数の配布方法があり、運用に応じて選択します。署名とプロビジョニングは CI での最大の障害源です。
- CocoaPods は開発・検証が手早いです。kotlin { cocoapods { ... } } で podspec を生成できます。
- XCFramework はバイナリ配布に向きます。xcodebuild -create-xcframework で結合します。
- SwiftPM は binaryTarget による配布が可能です。ZIP と checksum を用います。
- 実機(Device)向けビルドでは provisioning profile と teamId が必須です。CI での扱いに注意してください。
CocoaPods と Apple Silicon の注意:
- Apple Silicon では Homebrew のパスが /opt/homebrew です。CI スクリプトで PATH を通してください。
- CocoaPods のバージョン差でトラブルが出るため、Bundler 経由で固定することを推奨します。
Gemfile(例)
|
1 2 3 |
source 'https://rubygems.org' gem 'cocoapods', '1.12.1' |
インストール(Apple Silicon での回避例)
|
1 2 3 |
arch -x86_64 bundle install arch -x86_64 bundle exec pod install --repo-update |
iOS の署名と CI(実務メモ):
- シミュレータビルドでは CODE_SIGNING_ALLOWED=NO を指定すると署名を回避できます。
- 実機/App Store への配布では証明書(p12)と provisioning profile が必要です。
- CI では p12 と provisioning profile を Base64 にしてシークレットで保持し、ジョブ内で復元してインポートします。
- fastlane match を使うと証明書管理が簡単になります。
シミュレータ向けビルド例(xcodebuild):
|
1 2 3 4 5 6 7 8 |
xcodebuild -workspace iosApp.xcworkspace \ -scheme iosApp \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 14' \ -derivedDataPath build/ios \ CODE_SIGNING_ALLOWED=NO \ build |
XCFramework 生成例:
|
1 2 3 4 5 |
xcodebuild -create-xcframework \ -framework path/to/ios-arm64/Release/Framework.framework \ -framework path/to/ios-x86_64-simulator/Release/Framework.framework \ -output Shared.xcframework |
SwiftPM で binaryTarget を使う場合は ZIP を GitHub Release に置き、checksum を計算します。
JavaScript ターゲットとバンドリング
JS ターゲットは IR モードが原則推奨です。出力アセットの扱いと Web 側統合の例を示します。
- js(IR) は最適化と将来性で有利です。LEGACY は古いライブラリと互換性が必要な場合に限定します。
- KMP の出力は通常 shared/build/js/packages/
に配置されます。そこを Web プロジェクトから参照します。 - 開発時は file: パッケージで依存を張るか、npm に publish して npm/Yarn で参照します。
- Vite / webpack では alias を張って shared のビルド成果物を参照する運用が一般的です。
Vite の解決例(vite.config.ts の一部):
|
1 2 3 4 5 6 7 8 9 10 |
import path from "path"; export default { resolve: { alias: { "@shared": path.resolve(__dirname, "../shared/build/js/packages/shared") } } } |
Gradle でのパッケージ生成:
|
1 2 3 |
./gradlew :shared:jsBrowserProductionWebpack # 成果物は shared/build/distributions/ や shared/build/js/packages/shared に出ます |
npm へ公開する場合は npmPublish の設定や package.json の調整を行ってください。
CI・iOS署名・公開・セキュリティ
CI と公開は運用面で最も工数がかかる領域です。ここでは GitHub Actions を例に、iOS の実務的注意点、Apple Silicon 対策、公開手順、SBOM/脆弱性スキャンを示します。
GitHub Actions の実務例(詳細)
CI はプラットフォーム別にジョブを分割します。macOS ジョブは時間当たりのコストが高くなる点を念頭に置いてください。
簡易的なワークフロー例(要点抜粋):
|
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 |
name: CI on: [push, pull_request] jobs: build-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - uses: actions/setup-node@v4 with: node-version: 18 - name: Cache Gradle uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*','**/gradle-wrapper.properties') }} - run: ./gradlew build --no-daemon --parallel --stacktrace build-macos: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - name: Install CocoaPods run: | brew install cocoapods || true gem install bundler || true - name: CocoaPods install run: | cd iosApp arch -x86_64 bundle install || true arch -x86_64 bundle exec pod install --repo-update - name: Build iOS Simulator run: | ./gradlew :shared:assemble xcodebuild -workspace iosApp.xcworkspace -scheme iosApp \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 14' \ -derivedDataPath build/ios \ CODE_SIGNING_ALLOWED=NO \ build - uses: actions/upload-artifact@v4 with: name: ios-derived path: build/ios |
実務的なポイント:
- derivedDataPath を明示的に指定して成果物の保存とキャッシュを管理します。
- シミュレータビルドは CODE_SIGNING_ALLOWED=NO で署名を回避できますが、実機やリリース配布は別ジョブで処理します。
- macOS ランナーは Linux よりもコストが高いので、頻度を最小化する計画が必要です。目安として、ビルド時間を短縮することでランニングコストを大きく下げられます。
Apple Silicon と CocoaPods の実務対策
Apple Silicon 環境ではパスやアーキテクチャの違いで pod install 等が失敗しやすいです。回避策をまとめます。
- Homebrew のパス (/opt/homebrew) を CI スクリプトで PATH に追加します。
- CocoaPods は Bundler でバージョン固定し、arch -x86_64 を用いて Intel モードで pod install することが安定します。
- CocoaPods の推奨バージョンはプロジェクトで固定してください(例: 1.12.x)。CI で pod repo update を実行すると時間がかかるため注意します。
公開(Maven Central / Podspec / XCFramework / SwiftPM)
公開フローは多段階です。ここでは Sonatype OSSRH(Maven Central)と iOS 側の代表的な公開手順をまとめます。
公開手順(Maven Central / OSSRH の一般的な流れ):
- Sonatype OSSRH アカウントを取得し、プロジェクトを登録します。
- GPG 鍵を生成します。公開にはアーティファクトの署名が必要です。
- Gradle に maven-publish と signing、Nexus publish プラグインを設定します。
- CI のシークレットに OSSRH の認証情報と GPG 秘密鍵を格納します。
- ./gradlew publish を実行してステージングへアップロードし、Nexus 上でステージングを close → release します。
Nexus Publish プラグインの簡易設定例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
plugins { id("io.github.gradle-nexus.publish-plugin") version "1.3.0" `maven-publish` signing } nexusPublishing { repositories { sonatype { username.set(findProperty("ossrhUsername") as String?) password.set(findProperty("ossrhPassword") as String?) } } } |
iOS の公開(Podspec / XCFramework / SwiftPM):
- CocoaPods: podspec を作成し、pod lib lint で検証後、pod trunk push で公開します。
- XCFramework: 各アーキ用の framework を集めて xcodebuild -create-xcframework でまとめ、GitHub Release にアップロードする例が一般的です。
- SwiftPM: binaryTarget に URL と checksum を指定して配布します。checksum は swift package compute-checksum で算出します。
SBOM と依存脆弱性スキャン
配布物のトレーサビリティと脆弱性管理を組み込みます。
- SBOM は CycloneDX Gradle Plugin 等で生成できます(例: gradle cyclonedxBom)。
- Dependabot / Renovate で依存更新PRを自動生成します。
- Snyk や GitHub Advanced Security を CI に組み込み、PRで脆弱性チェックを走らせます。
CI 内の Snyk 例(簡易):
|
1 2 3 4 5 6 7 |
- name: Run Snyk test uses: snyk/actions@master with: args: test env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} |
よくあるトラブルと短い対処法
ここでは頻出エラーと初期対応を列挙します。まずはログ確認とツールチェーンのバージョン整合から着手してください。
- Gradle同期エラー:Kotlinプラグインと Gradle の互換性を確認する。
- Kotlin/Native のリンクエラー:対象アーキテクチャが有効かを再確認する(iosX64 / iosArm64 等)。
- CocoaPods のインストール失敗:Bundler 固定 + arch -x86_64 で再試行する。
- iOS 署名エラー:TEAM_ID と provisioning profile、certificate の組み合わせを見直す。
まとめ
Kotlin Multiplatform(KMP)はビジネスロジックの共有で工数削減が期待できますが、ツールチェーンと配布の運用が重要です。この記事では再現可能な最小リポジトリ、互換性マトリクス、iOS 署名・Apple Silicon 対策、CI と公開の実務手順を網羅しました。下記が要点です。
- 最小リポジトリでまずビルドを通すこと。settings.gradle.kts と build.gradle.kts を整備する。
- Kotlin / Gradle / 各ライブラリの互換性を表で管理し、定期的に更新する。
- iOS は署名とプロビジョニングが運用上の主要課題。CI での証明書管理を自動化する。
- Apple Silicon では CocoaPods の実行方法と Homebrew パスに注意する。
- 公開は GPG 署名と Sonatype ステージング手順を組み込む。SBOM と脆弱性スキャンで配布リスクを低減する。
参考にしたい具体手順やサンプルの補足が必要な場合は、上記のファイルツリーをそのままベースにしてローカルで作成し、段階的に機能を追加して検証してください。