Kotlin Multiplatform KMP実践:Composeでクロスプラットフォームアプリを構築する5つのコアパターン
编程语言
Kotlin Multiplatform:ひとつのコードベース、すべてのプラットフォーム
iOSはSwift、AndroidはKotlin、WebはTypeScript——クロスプラットフォーム開発のコストは依然として高い。Kotlin Multiplatform(KMP)は、同じKotlinコードでビジネスロジックを共有し、Compose MultiplatformでUIを共有、一度書けばAndroid、iOS、Desktop、Webで実行可能。2026年、KMPはクロスプラットフォーム開発の最適解の一つとなっています。
本記事では5つのコアパターンを通じて、expect/actualメカニズム→共有ビジネスロジック→Compose UI→プラットフォームAPI→プロダクションビルドのフルチェーン実践を解説します。
コア概念
| 概念 | 説明 |
|---|---|
| KMP | Kotlin Multiplatform Platform、Kotlinのクロスプラットフォームソリューション |
| expect/actual | KMPのプラットフォーム差異宣言と実装メカニズム |
| Compose Multiplatform | Jetpack ComposeベースのクロスプラットフォームUIフレームワーク |
| 共有ロジック | 複数プラットフォームで共用するビジネスロジックコード |
| プラットフォーム固有 | 特定プラットフォーム向けの実装コード |
| Kotlin/Native | ネイティブバイナリにコンパイルするKotlinバックエンド |
| Kotlin/JS | JavaScriptにコンパイルするKotlinバックエンド |
| 相互運用 | プラットフォームネイティブコード(Swift/JS/C)との連携 |
問題分析:KMPの5つの課題
- expect/actualの学習コスト:宣言と実装の分離パターンへの適応が必要
- Compose Multiplatformの成熟度:iOS版はまだBeta段階
- プラットフォーム差異の処理:iOS/Android/デスクトップ間のAPI差異が大きい
- デバッグ体験の悪さ:クロスプラットフォームデバッグチェーンが複雑
- ビルド設定の複雑さ:Gradleマルチプラットフォーム設定の学習曲線が急
ステップバイステップ:5つのKMP実装パターン
パターン1:KMPプロジェクト構造とexpect/actual
// commonMain/src/commonMain/kotlin/Platform.kt
expect class Platform() {
val name: String
}
fun getPlatform(): Platform {
return Platform()
}
// androidMain/src/androidMain/kotlin/Platform.kt
actual class Platform actual constructor() {
actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
// iosMain/src/iosMain/kotlin/Platform.kt
actual class Platform actual constructor() {
actual val name: String = "iOS ${platform.Foundation.NSProcessInfo.processInfo.operatingSystemVersionString}"
}
パターン2:共有ビジネスロジック層
// commonMain
class GreetingRepository {
private val platform = getPlatform()
fun greet(): String {
return "Hello from ${platform.name}!"
}
}
class UserViewModel {
private val repository = GreetingRepository()
private val _greeting = MutableStateFlow("")
val greeting: StateFlow<String> = _greeting.asStateFlow()
fun loadGreeting() {
_greeting.value = repository.greet()
}
}
パターン3:Compose Multiplatform UI
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun App() {
var greeting by remember { mutableStateOf("Loading...") }
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = greeting,
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { greeting = GreetingRepository().greet() }) {
Text("Load Greeting")
}
}
}
}
}
}
パターン4:プラットフォーム固有API呼び出し
// commonMain
expect class DateTimeFormatter() {
fun format(timestamp: Long): String
}
// androidMain
actual class DateTimeFormatter actual constructor() {
private val sdf = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
actual fun format(timestamp: Long): String = sdf.format(Date(timestamp))
}
// iosMain
actual class DateTimeFormatter actual constructor() {
actual fun format(timestamp: Long): String {
val date = NSDate(timeIntervalSince1970 = timestamp / 1000.0)
val formatter = NSDateFormatter().apply {
dateFormat = "yyyy-MM-dd HH:mm:ss"
}
return formatter.stringFromDate(date)
}
}
パターン5:プロダクションビルドとリリース
// build.gradle.kts
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
jvm("desktop")
js(IR) {
browser()
}
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
}
androidMain.dependencies {
implementation("androidx.activity:activity-compose:1.9.0")
}
}
}
落とし穴ガイド
落とし穴1:commonMainでプラットフォーム固有APIを参照
// ❌ 間違い:commonMainでAndroid APIを直接使用
fun getPath() = android.os.Environment.getDataDirectory().path
// ✅ 正しい:expect/actualで宣言
// commonMain
expect fun getDataPath(): String
// androidMain
actual fun getDataPath() = android.os.Environment.getDataDirectory().path
落とし穴2:Compose iOSのメモリリーク
// ❌ 間違い:iOSネイティブ参照を解放しない
class IOSBridge {
val delegate = object : NSURLSessionDelegate() { ... }
}
// ✅ 正しい:WeakReferenceまたは手動解放を使用
class IOSBridge {
private var delegateRef: WeakReference<NSURLSessionDelegate>? = null
fun cleanup() { delegateRef = null }
}
落とし穴3:Kotlin/Nativeの並行制限
// ❌ 間違い:スレッド間で可変状態を共有
var counter = 0 // 異なるスレッドからのアクセスでクラッシュ
// ✅ 正しい:AtomicReferenceを使用
val counter = AtomicReference(0)
落とし穴4:Gradle設定が複雑すぎる
// ❌ 間違い:すべての設定を1つのbuild.gradle.ktsに記述
// 300行以上のGradleファイル
// ✅ 正しい:convention pluginsで分割
// build-logic/convention/src/main/kotlin/KmpConventionPlugin.kt
class KmpConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.kotlin { /* 標準KMP設定 */ }
}
}
落とし穴5:プラットフォームフォント差異の未処理
// ❌ 間違い:プラットフォーム固有のフォント名を使用
Text(fontFamily = FontFamily("SF Pro"))
// ✅ 正しい:Compose内蔵フォントまたは条件分岐を使用
val fontFamily = if (Platform.isIOS) FontFamily("SF Pro") else FontFamily("Roboto")
エラートラブルシューティング
| # | エラー | 原因 | 解決方法 |
|---|---|---|---|
| 1 | Unresolved reference: expect |
KMPプラグインが未有効 | kotlin("multiplatform")プラグインを追加 |
| 2 | Actual class has no corresponding expect |
expect/actualの不一致 | commonMainとプラットフォームMainの宣言を確認 |
| 3 | Linker error iOS |
Kotlin/NativeとCの相互運用の問題 | cinterop設定と.defファイルを確認 |
| 4 | Compose compiler error |
Composeバージョンの非互換 | composeプラグインとcompilerバージョンを統一 |
| 5 | Gradle sync failed |
マルチプラットフォーム依存関係の競合 | version catalogでバージョンを統一 |
| 6 | JS compilation error |
Kotlin/JSはリフレクション非対応 | commonMainでのリフレクションAPI使用を避ける |
| 7 | iOS build timeout |
Kotlin/Nativeコンパイルが遅い | 増分コンパイルとキャッシュを有効化 |
| 8 | ClassNotFoundException desktop |
JVMモジュールの設定ミス | jvm()ターゲットとmainClassを確認 |
| 9 | Module not found |
sourceSetsパスエラー | src/commonMain/kotlinディレクトリ構造を確認 |
| 10 | SDK not found |
iOS SDKが未インストール | XcodeとCommand Line Toolsをインストール |
比較分析
| 次元 | KMP | Flutter | React Native | .NET MAUI |
|---|---|---|---|---|
| 言語 | Kotlin | Dart | JavaScript/TS | C# |
| UI共有 | Compose | Widget | JSX | XAML |
| ネイティブパフォーマンス | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| エコシステムの豊富さ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 学習曲線 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| ネイティブ相互運用 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| iOS成熟度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
まとめ:Kotlin Multiplatformはexpect/actualメカニズムとCompose Multiplatformにより、クロスプラットフォーム開発で独自の柔軟性を提供します。KMPはKotlin/Androidエコシステムを既に持つチームに適しており、段階的なコード共有が可能です。2026年、Compose MultiplatformのiOS版が成熟しつつあり、KMPはクロスプラットフォーム開発の現実的な選択肢となっています。
オンラインツール推薦
- JSONフォーマッター:/ja/json/format
- Hash計算:/ja/encode/hash
- cURL to Code:/ja/dev/curl-to-code
ブラウザローカルツールを無料で試す →
#Kotlin多平台#KMP#跨平台开发#Compose Multiplatform#2026#编程语言