「二重の作業」が午前2時の重荷になるとき
午前2時、画面はまだ明るいまま。Androidで非常に複雑なキャンペーン計算ロジックのバグを修正し終えたところだ。一息つこうとした瞬間、思い出した。「しまった、iOS版も残ってた!」。再びXcodeを開き、Swiftで同じロジックの箇所を探し、カンマ一つ打ち間違えていないか祈りながら書き直す。同じビジネスロジックのために2つのコードベースを並行して維持するのは、まさに悪夢だ。
この状況は決して他人事ではないだろう。2つのプラットフォームでデータ処理ロジックを何度も書くのは時間の無駄なだけでなく、データの不整合も招きやすい。以前、私はフィンテックアプリの5万行を超えるコードベースのリファクタリングを指揮したことがある。その時の教訓はこうだ。計算ロジックが断片化していると、たとえ仕様が全く同じでも、遅かれ早かれ「AndroidとiOSで挙動が違う」という事態に陥る。
Kotlin Multiplatform (KMP)とは一体何か?
KMPはFlutterやReact Nativeとは異なる。UIを独自の手法で再描画しようとはしない。代わりに、KMPはネイティブUI(AndroidならJetpack Compose、iOSならSwiftUI)をそのまま活かすことができる。開発者は、Data LayerやDomain Layer、APIコールといったビジネスロジックの共有だけに集中すればいいのだ。
技術的には、KMPはKotlinコードをAndroid向けにはバイトコードに、iOS向けにはネイティブフレームワークにコンパイルする。パフォーマンスは純粋なネイティブ開発とほとんど変わらない。必要であれば、いつでもOS固有のAPIにアクセスすることが可能だ。
Expect/Actual機構 — 万能の鍵
UUIDの取得やKeychainへのアクセスなど、完全に共通化できない部分もある。そこで活躍するのが expect と actual のペアだ。共通(common)モジュールで expect 関数を宣言し、各プラットフォームで個別に actual を実装する。非常に明快で堅牢な仕組みだ。
最初のSharedモジュールを構築する
まずはAndroid StudioにKotlin Multiplatformプラグインをインストールしよう。最も手っ取り早いのは、KMP Wizardを使って標準的なプロジェクト構造を作成することだ。
1. プロジェクトのディレクトリ構造
一般的なKMPプロジェクトは以下のように分かれている。
- composeApp/androidMain: Android専用のUIとロジックを格納。
- iosApp: オリジナルのSwiftUIコードを含むXcodeプロジェクト。
- shared/commonMain: プロジェクトの「心臓部」。共有されるビジネスロジック。
- shared/iosMain & shared/androidMain: 各OS固有のコードを実装する場所。
2. 共有ロジックを書く
例えば、メールアドレスの形式チェックが必要な場合。2箇所に書く代わりに、shared/src/commonMain/kotlin/Validator.kt に一度だけ記述する。
class Validator {
fun isValidEmail(email: String): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"
return email.matches(emailRegex.toRegex())
}
}
これでAndroidとiOSの両方が「Single Source of Truth(唯一の真実のソース)」を共有することになる。もし正規表現の変更を求められても、修正するのはたった1行だけだ。このような小さな変更に伴うメンテナンス時間を少なくとも50%は削減できる。
3. プラットフォーム固有の部分を処理する
OSのバージョンを取得したい場合はどうするか? commonMain で次のように宣言する。
// commonMain内
expect fun getPlatformName(): String
androidMain での実装:
actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
そして iosMain での実装:
import platform.UIKit.UIDevice
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
KMPエコシステム:必須ツール
KMPを効率的に開発するには、純粋なJavaライブラリではなく、マルチプラットフォーム対応のライブラリが必要だ。現在、コミュニティからは非常に強力なツールセットが提供されている。
- Ktor: ネットワーク処理(Retrofitの完璧な代替)。
- SQLDelight: データベース管理(KMP環境において Roomよりも強力で安全)。
- Kotlinx.serialization: 高速なJSONパース。
- Koin: 超軽量な依存性の注入(DI)。
私の経験から言えば、最初から100%のロジックを移行しようとしないでほしい。まずはValidatorやData Modelのような、小さく独立したモジュールから始めよう。Gradleの扱いに慣れてきたら、RepositoryやAPIへと段階的に進めていくのがいい。
血の教訓:テストカバレッジを忘れるな
ロジックを共有すれば終わり、と勘違いしがちだ。しかし実際には、異なるランタイム(AndroidのJVMとiOSのNative)で動作するため、挙動に差異が生じることがある。現在のKotlin/Nativeには非常に安定したNew Memory Managerがあるとはいえ、慎重に越したことはない。
リファクタリングの前には、必ず commonTest で入念にユニットテストを書くようにしている。両方の環境でテストがパスして初めて、自信を持ってデプロイできる。ユーザーからバグ報告を受けてから慌ててiOSでデバッグするような事態は、非常に体力を消耗するので避けるべきだ。
結び
KMPはすべてのプロジェクトに対する魔法の杖ではないが、ネイティブのパフォーマンスと開発スピードのバランスが最も取れた解決策だ。2つのチームで同じ作業を繰り返す代わりに、ユーザー体験の最適化にリソースを集中させることができる。
もし新しいモバイルプロジェクトを計画しているなら、ぜひKMPを試してみてほしい。2つのプラットフォーム間でロジックをコピペするためだけに無意味な徹夜をする必要がなくなるはずだ。スムーズなアプリ開発体験を楽しんでほしい!

