Here is a blog post written specifically for My Core Pick.
同じコードを二度書くのはやめよう:Kotlin Multiplatformでビジネスロジックを共有する
Android版のアプリのバグを修正し、その3週間後にまったく同じバグがiOSユーザーを苦しめていることに気づいた経験がある方。手を挙げてください。
誰もが通る道です。
これはモバイル開発における典型的な苦悩です。2つのコードベース。2つのチーム(あるいは1人の非常に疲れたフリーランサー)。2つの言語。
しかし、プロダクトはたった1つです。
長年、この問題に対する業界の解決策は「ハイブリッド」フレームワークでした。私たちはPhoneGapを試しました。React Nativeと格闘しました。Flutterにも手を出しました。
これらのツールは素晴らしいものですが、多くの場合、ユーザーインターフェース(UI)において妥協を求められます。それらのエコシステムの中で、ルックアンドフィールをゼロから再構築しなければなりません。
しかし、ネイティブUIを維持したい場合はどうでしょうか? SwiftUIやJetpack Composeが大好きな場合は?
アプリの「顔」ではなく、「頭脳」だけを共有したいとしたらどうしますか?
そこで登場するのが Kotlin Multiplatform (KMP) です。
ここ My Core Pick では、ハードワークではなく、スマートに働くことを信条としています。今日は、なぜKMPがビジネスロジック共有の未来なのか、そしてどうすれば同じ作業の繰り返しを防げるのかをお話しします。
「デュアルプラットフォーム」の罠

解決策に飛び込む前に、問題について話しましょう。
iOSとAndroidでネイティブアプリを構築する場合、単にUIを二度書いているだけではありません。
すべて を二度書いているのです。
SwiftとKotlinで通信レイヤーを書きます。
CoreDataとRoomでデータベースロジックを書きます。
データ検証ロジック(このメールアドレスは有効か?)を2つの異なるファイルに書きます。
これこそが冗長性の定義です。
問題は最初にコードを書く時間だけではありません。本当の苦痛は後からやってきます。
それはメンテナンスの時です。
Androidでビジネスルールを変更した場合、iOSでも変更することを覚えておかなければなりません。そうしないと、アプリの挙動が一貫しなくなります。
ユーザーはそれに気づきます。理由はわからなくても、アプリにどこか「違和感」を覚えるのです。
この重複はバグの発生領域を広げます。テストの要件を倍増させます。そして、認知負荷(Cognitive Load)も倍増させます。
ロジック層を扱う、もっと良い方法があるはずです。
Kotlin Multiplatformとは正確には何か?

Kotlin Multiplatformは、従来の意味でのフレームワークではありません。
画面にキャンバスを描画する「一度書けばどこでも動く(Write once, run anywhere)」ブラックボックスでもありません。
KMPは、プラットフォーム間でコードを共有するために設計されたSDKです。
Android向けにはKotlinコードをJVMバイトコードにコンパイルします。ここまでは簡単です。
しかし魔法のような点は、同じ KotlinコードをiOS向けにLLVMバイトコードにコンパイルできることです。つまり、Appleのハードウェア上でネイティブに動作します。
Xcodeにとって、共有されたKotlinコードは単なるObjective-Cフレームワークに見えます。
他のネイティブライブラリと同じようにインポートし、関数を呼び出し、データを読み取ることができます。
ここでの哲学はシンプルです: ロジックは共有、UIはネイティブ。
美しいSwiftUIビューを維持できます。パフォーマンスの高いJetpack Composeレイアウトも維持できます。
単に、それらを動かすためのコードを二度書くのをやめるだけです。
実際に何を共有すべきか?

私がKMPを始めた当初、すべてを共有しようとしました。
それは間違いでした。
KMPの力は、どこで線を引くかを知ることから生まれます。ボタン、アニメーション、画面遷移などを共有しようとしてはいけません。
共有すべきはビジネスロジックです。
では、実際にはどのような形になるのでしょうか? ここでは、私たちが焦点を当てている3つの主要な柱を紹介します。
1. データレイヤー
これは最も手を付けやすい部分(Low-hanging fruit)です。
すべてのアプリはインターネットと通信する必要があります。すべてのアプリはJSONをパースする必要があります。
従来のセットアップでは、Swiftに Codable 構造体があり、Kotlinにデータクラスがあります。バックエンドが変更されれば、両方を更新する必要があります。
KMPを使えば、データモデルを書くのは一度だけで済みます。
通信には Ktor のようなライブラリを使い、パースには Kotlin Serialization を使います。
データを取得し、パースし、エラーを処理するリポジトリクラスを書きます。
iOSアプリとAndroidアプリは、単に repository.getData() を呼び出すだけです。データがどのようにしてそこに来たかは気にしません。ただ表示するだけです。
2. ローカルストレージ
データベース管理は複雑です。
2つの異なるプラットフォームでマイグレーションを管理するのは悪夢です。
KMPでは、SQLDelight や Room(現在はKMPをサポートしています)のようなライブラリを使用します。
データベーススキーマを一箇所で定義します。SQLクエリを一箇所で書きます。
生成されたコードが、プラットフォーム固有のドライバーを処理します。
AndroidアプリはSQLiteドライバーを使用し、iOSアプリはネイティブドライバーを使用します。
ユーザーデータの保存、取得、削除を管理するロジックは、両方のプラットフォームで完全に同一になります。
3. 「頭脳」(ViewModels)
ここからが本当に面白いところです。
実は、ViewModelも共有できるのです。
ViewModelは画面の状態を保持するコンポーネントです。ユーザーがボタンをクリックしたときに何が起こるかを決定します。ネットワークが失敗したときに何を表示するかを決定します。
これは純粋なロジックです。ピクセルとは何の関係もありません。
ViewModelを共有することで、UIは驚くほど「愚直(dumb)」になりますが、これは良いことです。
SwiftUIビューは、共有されたKotlinコードからの状態オブジェクトを監視するだけです。
状態が isLoading = true であれば、UIはスピナーを表示します。
UIは なぜ ロード中なのかを知る必要はありません。ただ命令に従うだけです。
仕組み:技術的なのぞき見
あなたが何を考えているか分かります。
「素晴らしそうだけど、セットアップが悪夢のように大変なのでは?」
かつてはそうでした。しかし、エコシステムはこの1年で大きく成熟しました。
プロジェクト構造を見てみましょう。
ソースセット(The Source Sets)
KMPプロジェクトは「ソースセット」に分かれています。
まず、commonMain があります。
ここにコードの90%が存在します。純粋なKotlinです。モデル、リポジトリ、ロジックが含まれます。
次に、androidMain と iosMain があります。
これらのフォルダはプラットフォーム固有のコード用です。
純粋なアルゴリズムを書いているなら、それは commonMain に入ります。
しかし、カメラやBluetoothのような特定のデバイス機能にアクセスする必要がある場合もあります。
KotlinはデフォルトではiOSのカメラにアクセスする方法を知りません。
Expect/Actual メカニズム
これが共有世界とネイティブ世界の架け橋となります。
これは言語レベルでのインターフェースのような働きをします。
commonMain フォルダに次のように書いたとします:
expect fun getPlatformName(): String
コンパイラは文句を言います。関数を約束したのに、実装を提供していないからです。
そこで、androidMain に次のように書きます:
actual fun getPlatformName(): String = "Android"
そして iosMain には次のように書きます:
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName()
iosMain では UIDevice にアクセスできていることに注目してください。これはAppleのAPIです。
KMPはiOS SDKにアクセスできるため、Kotlin内部で直接アクセスできるのです。
このメカニズムにより、高レベルのロジックを共有しながら、必要なときにはいつでもネイティブ機能を利用することができます。
実際のメリット
私たちは最近、My Core Pick のプロジェクトでKMPを導入しました。
その結果は、私のモバイル開発に対する見方を変えました。
一貫性は王様
最大のメリットはスピードではありませんでした。一貫性です。
アプリ内に「健康スコア」の複雑な計算がありました。
以前は、iOS開発者が要件をある方法で解釈し、Android開発者が別の方法で解釈していました。同じデータに対して微妙に異なるスコアが表示される結果になっていました。
KMPでは、アルゴリズムを一度だけ書きました。
Kotlinで徹底的にユニットテストを行いました。
そしてデプロイしました。
ユーザーがデバイスを切り替えても、スコアが完全に同じになることが確実になりました。
機能改善の高速化
アーキテクチャが整うと、新機能の追加が大幅に速くなりました。
新しいAPIエンドポイントを追加する必要がある場合、共有モジュールに追加しました。
すると突然、iOSとAndroidの両方の開発者が即座にそれにアクセスできるようになりました。
iOS開発者が通信レイヤーの実装を追いつかせるのを待つ必要はありませんでした。
これにより「リレー競争」のようなダイナミクスが生まれます。コアロジックの開発者が道を切り開き、UI開発者がゴールに向かってダッシュするのです。
テストの強化
UIのテストは困難です。ビジネスロジックのテストは(比較的)簡単です。
ロジックがOSから分離されているため、JVM上でテストを実行できます。
これは爆速です。ViewModelをテストするためにAndroidエミュレーターやiOSシミュレーターを起動する必要はありません。
標準的なJUnitテストを書きます。それらは数ミリ秒で実行されます。
これにより、より多くのテストを書くようになり、結果としてより安定したプロダクトにつながります。
本番利用の準備はできているか?
新しい技術の採用をためらうかもしれません。
それは健全な懐疑心です。
しかし、KMPは巨大企業によって使用されています。
Netflixが使っています。VMWareが使っています。マクドナルドが使っています。
Googleも公式にサポートしています。
ライブラリのエコシステムも活況を呈しています。
依存性注入(DI)には Koin があります。
ネットワーキングには Ktor があります。
ロギングには Napier があります。
ツールは揃っています。サポートもあります。
結論
私たちはモバイル開発の新しい時代にいます。
iOSとAndroidのロジックを厳密に分離(サイロ化)する時代は終わりつつあります。それにはあまりにも多くのコストと時間がかかります。
しかし、私たちはネイティブUIの品質を犠牲にするつもりもありません。
Kotlin Multiplatformはそのスイートスポット(最適解)を突いています。
重要な部分(UI)ではプラットフォームの違いを尊重し、合理的な部分(ロジック)ではプラットフォームの類似性を統一します。
同じコードを二度書くのはやめましょう。
同じバグを二度直すのはやめましょう。
ビジネスロジックの共有を始めましょう。あなたのコードベース(そしてあなたの正気)は、きっとあなたに感謝するはずです。
この詳細な解説を楽しんでいただけましたか? ここ My Core Pick で、さらなる技術的なインサイトをチェックしてください。