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つの課題

  1. expect/actualの学習コスト:宣言と実装の分離パターンへの適応が必要
  2. Compose Multiplatformの成熟度:iOS版はまだBeta段階
  3. プラットフォーム差異の処理:iOS/Android/デスクトップ間のAPI差異が大きい
  4. デバッグ体験の悪さ:クロスプラットフォームデバッグチェーンが複雑
  5. ビルド設定の複雑さ: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はクロスプラットフォーム開発の現実的な選択肢となっています。


オンラインツール推薦

ブラウザローカルツールを無料で試す →

#Kotlin多平台#KMP#跨平台开发#Compose Multiplatform#2026#编程语言