Kotlin Multiplatform vs Flutter
KMP(ビジネスロジック共有+ネイティブUI)vs Flutter(UI+ランタイム共有)——クロスプラットフォームモバイル比較。パフォーマンス、開発者体験、エコシステム。
ビジネスロジックだけを共有し、UIはネイティブのまま
単一のコードベースからUIとビジネスロジックを共に生成
明確な勝者はなく、シナリオによって決まる。UIをネイティブのまま残し、ネットワーク/データ/ビジネスロジックのみを共有したい場合で、チームがKotlin/Androidに近いならKMPを選ぶとよい。shared moduleをAndroidに追加し、iOSにXcodeフレームワークとして接続する手順は公式チュートリアルで明確に定義されている。単一のコードベースから画面を生成し、Webチームをモバイルに参加させたい場合はReact Nativeが有力だ。126,703スターのエコシステムと公式の「Integration with Existing Apps」ガイドがそれを裏付けている。
| カテゴリー | Kotlin Multiplatform | React Native |
|---|---|---|
| パフォーマンス | 8/10 | 7/10 |
| 学習のしやすさ | 6/10 | 7/10 |
| エコシステム | 6/10 | 9/10 |
| コミュニティ | 6/10 | 9/10 |
| 求人市場 | 6/10 | 7/10 |
| 将来性 | 8/10 | 8/10 |
// KMP shared module — 既存のAndroid/iOSアプリへの追加
// shared/src/commonMain/kotlin/data/UserRepository.kt
package com.app.shared.data
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
@Serializable
data class User(val id: String, val name: String, val avatarUrl: String)
// 注: kotlin.Resultはvalue classであり、Objective-Cのexportには公開されない。
// 戻り値の型は直接User、エラーはthrows/completion errorとして伝播する。
class UserRepository(private val api: UserApi) {
suspend fun fetchUser(id: String): User = api.getUser(id)
}
// expect/actual: プラットフォーム固有の部分(iOS Keychain / Android EncryptedSharedPreferences)
expect class SecureStorage {
fun save(key: String, value: String)
fun read(key: String): String?
}
// shared/build.gradle.kts(抜粋)
kotlin {
androidTarget()
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
it.binaries.framework { baseName = "Shared" }
}
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:3.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
}
}
}
// iOS側でXcodeフレームワークとして接続する(Swift)
import Shared
let repo = UserRepository(api: UserApiImpl())
repo.fetchUser(id: "42") { user, error in
if let user = user { print(user.name) }
}// React Native — 既存アプリへの統合; index.js: 画面を登録
import { AppRegistry } from 'react-native';
import ProfileScreen from './src/ProfileScreen';
AppRegistry.registerComponent('ProfileScreen', () => ProfileScreen);
// src/ProfileScreen.tsx — Strict TypeScript API(RN 0.87デフォルト)
import { View, Text } from 'react-native';
export default function ProfileScreen({ userId }: { userId: string }) {
return (
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 17 }}>ユーザー {userId}</Text>
</View>
);
}
// android/app/build.gradle — RN Gradle Plugin(抜粋)
apply plugin: "com.facebook.react"
react { autolinkLibrariesWithApp() }
// MyReactActivity.kt — 公式ガイドのパターン
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
class MyReactActivity : ReactActivity() {
override fun getMainComponentName(): String = "ProfileScreen"
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
// AndroidManifest.xml: <activity android:name=".MyReactActivity"
// android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
// 既存の Activity から: startActivity(Intent(this, MyReactActivity::class.java))明確な勝者はなく、シナリオによって決まる。UIをネイティブのまま残し、ネットワーク/データ/ビジネスロジックのみを共有したい場合で、チームがKotlin/Androidに近いならKMPを選ぶとよい。shared moduleをAndroidに追加し、iOSにXcodeフレームワークとして接続する手順は公式チュートリアルで明確に定義されている。単一のコードベースから画面を生成し、Webチームをモバイルに参加させたい場合はReact Nativeが有力だ。126,703スターのエコシステムと公式の「Integration with Existing Apps」ガイドがそれを裏付けている。
無料相談を受ける明確な正解はなく、チームのスキルに依存します。Kotlin/AndroidチームでUIをネイティブのまま残したいならKMP、Web/Reactチームで単一のコードベースから画面を生成したいならReact Nativeです。どちらも既存アプリへの段階的な追加を公式にサポートしています。