Kotlin Multiplatform 2026: AndroidとiOSでコード共有
2026年のKotlin MultiplatformがAndroidとiOSでビジネスロジックを共有する方法を解説します。Gradle設定、expect/actual、Ktor、Compose Multiplatform、KMPの面接質問までを網羅します。

Kotlin Multiplatform (KMP) は、1つのKotlinコードベースでAndroidとiOS両方のアプリのビジネスロジックを動かしつつ、各プラットフォームが完全にネイティブなUI、パフォーマンス、SDKへのアクセスを維持できる技術です。2026年において、KMPはもはや実験段階ではありません。安定版であり、Google が公式に支援し、Netflix、McDonald's、Forbes、Cash App の本番環境で稼働しています。本稿では、コード共有が実際にどのように機能するのか、共有モジュールをどう構成するのか、そしてKMPの面接に臨む前に理解しておくべきトレードオフを詳しく解説します。
Kotlin Multiplatform が共有するのはコンパイル済みのロジックであり、ランタイムではありません。共通のKotlinは、Android向けにJVMバイトコードへ、iOS向けに (LLVM経由で) ネイティブバイナリへコンパイルされます。JavaScriptブリッジも、埋め込みWebViewも、リフレクション層も存在しないため、共有コードは両プラットフォームでネイティブと同じ速度で動作します。
ネイティブパフォーマンスを犠牲にせずKotlin Multiplatformがコードを共有する仕組み
中心的な仕組みは、ソースセットの階層構造です。commonMain には、ネットワーク処理、シリアライズ、ビジネスルール、ビューモデルといったプラットフォーム非依存のKotlinを配置します。androidMain や iosMain のようなプラットフォーム別ソースセットには、プラットフォームAPIに触れるコードを配置します。コンパイラは、Android向けに commonMain と androidMain からJVMアーティファクトを、iOS向けに commonMain と iosMain からネイティブフレームワークを生成します。
2026年のKotlin Multiplatformは、単一の信頼できる情報源から真に恩恵を受けるもの、つまりモデル、APIクライアント、キャッシュ、バリデーションの共有を重視します。UIはCompose Multiplatformで共有することも、画面単位でSwiftUIによる完全ネイティブのまま残すこともできます。この「ロジックは共有し、UIは選ぶ」というモデルこそが、既存アプリを書き直さずにKMPを採用できる理由です。
Kotlin Multiplatformは、1つの共有モジュールを複数のターゲット (JVM上のAndroid、ネイティブとしてのiOS、さらにデスクトップとWeb) にコンパイルするKotlin技術です。そのため、ビジネスロジックは一度書けばよく、各プラットフォームはネイティブUIと完全なSDKアクセスを保持します。現在Googleはこれを第一級の選択肢として支援しており、Room、DataStore、ViewModel、PagingといったJetpackライブラリがKMPアーティファクトを提供しています。
共有モジュールに何を含めるかを決めることは、KMPプロジェクトにおいて最も重要なアーキテクチャ上の判断です。以下の表は、2026年に多くの本番チームがどこで線引きするかを表しています。プレゼンテーション層より下はすべて共有の有力候補であり、一方でプラットフォームのUX慣習に触れるものは通常ネイティブのまま残します。
| レイヤー | commonMainで共有? | 理由 | |-------|---------------------|--------| | データモデルとDTO | はい | 両プラットフォームで同一のAPI契約 | | ネットワークとキャッシュ | はい | HTTPスタックは1つ、バグの表面積も1つ | | ビジネスルールとバリデーション | はい | ドメインはOSごとに変わらない | | ビューモデルと状態 | 通常は共有 | ComposeもSwiftUIも共有状態を利用する | | UIレンダリング | 任意 | Composeで共有、または画面ごとにネイティブ | | プラットフォームAPI (カメラ、生体認証) | いいえ | 代わりにexpect/actualで公開する |
得られる効果は、アプリがこの表の下半分をどれだけ占めるかに比例して大きくなります。UIが薄くデータ中心の製品ほど共有量が多く、高度にカスタマイズされアニメーション主導のアプリほど共有量は少なくなります。どちらの極端も間違いではありません。だからこそKMPは、全か無かの書き直しではなく段階的な導入に適しています。
Gradleで共有モジュールを構成する
KMPモジュールは、Kotlin DSLでターゲットとソースセットを宣言します。次の build.gradle.kts は、Androidと3つのiOSアーキテクチャ (実機、Intelシミュレータ、Apple Siliconシミュレータ) を設定し、iOSの出力を Shared という名前のフレームワークとして公開します。
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
}
kotlin {
// Android target compiles commonMain + androidMain to JVM bytecode
androidTarget()
// Three iOS targets: device, Intel sim, Apple Silicon sim
listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared" // becomes "import Shared" in Swift
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:3.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:3.3.0")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:3.3.0")
}
}
}各ターゲットはプラットフォーム固有のKtorエンジン (Androidでは okhttp、iOSでは darwin) を取り込む一方、共有コードは共通の ktor-client-core APIのみに依存します。設定可能なオプションの全体像は Kotlin Multiplatformの公式ドキュメント にまとまっています。
expectとactualでプラットフォーム固有のコードを書く
一部のロジックは、プラットフォームAPIを必要とするため commonMain に置けません。expect/actual の仕組みは、共通コードで形状を宣言し、ターゲットごとに具体的な実装を供給します。解決はコンパイル時に行われ、ランタイムコストはゼロです。
// commonMain declares what it needs but cannot implement here
expect class Platform() {
val name: String
val osVersion: String
}
// Shared logic can now depend on Platform freely
fun analyticsUserAgent(): String {
val platform = Platform()
return "SharpSkill/" + platform.name + " " + platform.osVersion
}Android実装は Build クラスから、iOS実装は UIDevice から値を読み取ります。どちらも同じ契約を満たすため、commonMain は自分がどちらと対話しているかを知ることはありません。
import android.os.Build
// actual supplies the Android-specific implementation
actual class Platform actual constructor() {
actual val name: String = "Android"
actual val osVersion: String = Build.VERSION.RELEASE
}import platform.UIKit.UIDevice
// actual uses UIKit directly, no Objective-C bridging code required
actual class Platform actual constructor() {
actual val name: String = "iOS"
actual val osVersion: String = UIDevice.currentDevice.systemVersion
}Kotlin/NativeはiOS SDK全体に対して型付きのバインディングを同梱しているため、UIDevice、NSUserDefaults、Foundationの型は、手書きの繋ぎコードなしにKotlinから呼び出せます。
ネットワークとシリアライズのロジックを共有する
ネットワーク処理は、API契約が両プラットフォームで同一であるため、共有する価値が最も高いコードです。1つの Ktor クライアントと kotlinx.serialization が、2つの並行実装を置き換えます。suspend 関数は Kotlinコルーチン に依存しており、両サイドで async/await へきれいに対応します。
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
// One serializable model, zero duplication across platforms
@Serializable
data class Question(val id: String, val prompt: String, val difficulty: String)
// One HttpClient, one JSON parser, one repository for both apps
class InterviewRepository {
private val client = HttpClient {
install(ContentNegotiation) { json() }
}
suspend fun fetchQuestions(tech: String): List<Question> =
client.get("https://api.sharpskill.dev/questions/" + tech).body()
}このリポジトリ、そのリトライロジック、そしてデータモデルは一度だけ書かれます。パーサーで修正されたバグは、AndroidとiOSで同時に修正されます。ここがKMPが導入コストを回収する部分です。同じパターンは、SQLDelightによる共有データベース、共有キーバリューストレージ、Koinによる共有依存性注入にも広がるため、データ層全体をUIが依存するインターフェースの背後で commonMain に置くことができます。
よくある間違いは、早すぎる段階で多くを共有しようとすることです。現実的な進め方は、まず1つのリポジトリから始め、両プラットフォームでビルドとCIパイプラインを実証し、それから自信が深まるにつれて機能を1つずつ移していくことです。
Androidの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
Compose Multiplatformで共有UIを構築する
Compose Multiplatformは、2025年5月にiOSで安定版に到達し (バージョン1.8.0)、2026年半ばには1.11.0に達しました。ネイティブのテキスト入力と、デフォルトで有効な並行レンダリングを備えています。commonMain にある @Composable は両プラットフォームでレンダリングされ、共有ビューモデルは MVVMまたはMVIアーキテクチャ と自然に組み合わさります。
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding
// This list renders identically on Android and iOS
@Composable
fun QuestionList(questions: List<Question>) {
LazyColumn {
items(questions) { question ->
Card(modifier = Modifier.padding(8.dp)) {
Text(text = question.prompt, modifier = Modifier.padding(16.dp))
}
}
}
}UIの共有は任意で、画面単位です。チームは Compose Multiplatform でインターフェース全体を共有することも、アプリ全体をSwiftUIのままにしてロジックだけを共有することも、両者を混在させることもできます。ウィジェットやApp Clipのようにプラットフォーム固有のUXに依存する画面は、通常ネイティブのまま残します。
iOSでSwiftから共有コードを利用する
共有モジュールは、Swiftがあらゆるライブラリと同じようにインポートするフレームワークにコンパイルされます。歴史的な摩擦は、Kotlinの suspend 関数と Flow が扱いにくいコールバックAPIとして表出することでした。Touchlabの SKIE はこれを解決し、慣用的なSwiftを生成します。suspend はネイティブの async/await に、Flow は AsyncSequence になります。
import SwiftUI
import Shared // the KMP framework, baseName = "Shared"
struct ContentView: View {
@State private var questions: [Question] = []
private let repository = InterviewRepository()
var body: some View {
List(questions, id: \.id) { question in
Text(question.prompt)
}
.task {
// With SKIE, the Kotlin suspend fun is a Swift async fun
questions = (try? await repository.fetchQuestions(tech: "android")) ?? []
}
}
}iOSチームは通常のSwiftとSwiftUIを書きます。彼らは共有モデルとリポジトリを、ネイティブのSwiftパッケージとまったく同じように利用するため、KMPはiOSコードベースの大部分から見えないままです。
面接で言及する価値のある粗い部分がまだ2つあります。1つ目はビルド時間です。Kotlin/Nativeのコンパイルは純粋なSwiftのビルドより遅いものの、Gradleのビルドキャッシュと事前ビルドされたXCFrameworkの配布が日々のコストを和らげます。2つ目は、SwiftからKotlinへの境界を越えたデバッグです。改善されてはいるものの、1つの言語にとどまる場合ほどまだシームレスではありません。これも、チームが共有範囲を無秩序に広げず意図的に保つもう1つの理由です。
予想されるKotlin Multiplatformの面接質問
KMPの面接質問はAndroidやモバイルの職種でますます登場するため、幅広い Android面接対策 と並行して、繰り返し出るいくつかを練習しておく価値があります。
- expect/actualはインターフェースとどう違いますか? expect/actualは、ランタイムのディスパッチなしにターゲットごとにコンパイル時で解決され、クラス、関数、プロパティ、typealiasの裏付けとなれます。インターフェースはランタイムで動的に解決されます。プラットフォームAPIにはexpect/actualを、共有コード内の多態性にはインターフェースを使います。
- Kotlin/Nativeはメモリとスレッドをどう扱いますか? Kotlin 1.7.20以降、新しいメモリマネージャが従来のオブジェクトフリーズモデルを廃止したため、共有される可変状態とコルーチンは、JVMとほぼ同じようにスレッドをまたいで動作します。
- 既存アプリはKMPを段階的に採用できますか? はい。共有モジュールはAndroid向けにAAR、iOS向けにXCFrameworkとして配布されるため、書き直しなしで1つのリポジトリや機能を一度に移行できます。
- Compose Multiplatformを使わずにUIをネイティブのまま残すべきなのはどんなときですか? 画面がプラットフォーム固有のUXに依存するとき、あるいはiOSチームがプレゼンテーション層を所有し自分で制御したいときです。
K2コンパイラを備えたKotlin 2.3、ネットワーク処理にはKtor 3.x、共有データベースにはSQLDelight、依存性注入にはKoinをターゲットにします。iOSのインターオペには、早い段階でSKIEを追加します。Swift層を書いた後に後付けするのは、はるかに苦痛です。
まとめ
2026年のKotlin Multiplatformは、ネイティブUIやパフォーマンスを手放すことなく、AndroidとiOSにまたがる重複ロジックを削減する現実的な方法です。
- 単一の信頼できる情報源を持つロジック (モデル、ネットワーク、バリデーション) を共有し、画面ごとにネイティブUIかCompose UIかを選びます。
- プラットフォームAPIには
expect/actualを使います。コンパイル時に解決され、ランタイムのオーバーヘッドはありません。 - Ktorと
kotlinx.serializationでネットワーク処理を一度だけ書けば、修正が両プラットフォームに同時に反映されます。 - 書き直しにコミットするのではなく、AARとXCFrameworkを通じて段階的に採用します。
- 最初からSKIEを追加し、Swiftが
suspendとFlowをネイティブのasync/awaitとAsyncSequenceとして利用できるようにします。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

Android 16完全解説:新API、デスクトップモード、面接対策まで
Android 16(API 36)の新機能を徹底解説。Edge-to-edge強制適用、デスクトップモード、ProgressStyle通知、予測型バックナビゲーションなど、2026年のAndroid開発者面接で必須のトピックを網羅します。

Kotlin Flow vs StateFlow vs SharedFlow:2026年のAndroid面接質問
2026年にAndroidの面接官が問う Kotlin Flow・StateFlow・SharedFlow の質問を、明快な回答・比較表・本番投入できるコードとともに解説します。

Android依存性注入:Hilt vs Koin 完全ガイドと面接質問集 2026
HiltとKoinの違いをコード例・パフォーマンスベンチマーク・面接頻出質問で徹底比較。Hilt 2.57、Koin 4.2対応の2026年最新ガイド。