【ITニュース解説】Dependency Gaps in Compose Multiplatform (and how I solved them)
2025年09月19日に「Dev.to」が公開したITニュース「Dependency Gaps in Compose Multiplatform (and how I solved them)」について初心者にもわかりやすく解説しています。
ITニュース概要
Compose MultiplatformはUIやロジックを共有できるが、全ての環境で一元化は難しい。不足機能は`expect/actual`で各プラットフォーム向けに実装が必要だが、コード重複が課題だった。筆者は、Gradleモジュールで特定のプラットフォーム群の共通コードをまとめ、重複を減らす解決策を提示した。
ITニュース解説
現代のソフトウェア開発において、一つのソースコードから複数の異なる環境で動作するアプリケーションを作る「一度書けばどこでも動く」という考え方は、開発効率を高める上で非常に重要だ。この理想を追求する中で、JetBrains社が開発したKotlin Multiplatform (KMP) は、アプリケーションの主要なロジック部分を共通化し、AndroidやiOSといったプラットフォーム間で共有できるようにしてきた。しかし、ユーザーインターフェース(UI)の部分は各プラットフォームのネイティブ技術で個別に実装する必要があったため、完全なコード共有には至っていなかった。
その一歩先を行くのが、KMPの上に構築されたCompose Multiplatformというフレームワークだ。これは、アプリケーションのロジックだけでなく、UIのデザインや動作までも共通のコードで記述し、それをAndroid、iOS、Web (Wasmと呼ばれる技術を使用)、そしてデスクトップアプリケーション(JVMと呼ばれる技術を使用)といった多岐にわたるプラットフォームで動かすことを可能にする。これにより、開発者は一度書いたコードで、見た目も振る舞いもほぼ同じアプリケーションを、より多くのユーザーに提供できるという大きなメリットが生まれる。これはまさに「UIもロジックも一度書けばどこでも動く」という理想への大きな前進だと言えるだろう。
しかし、この画期的なCompose Multiplatformをもってしても、すべてのプラットフォームの多様な要件をたった一つのコードベースで完全に満たすことは、まだ難しい現実がある。各プラットフォームにはそれぞれ固有の機能や特性があり、時には共通のコードでは対応しきれない部分、「ギャップ」が生じてしまうのだ。例えば、ある機能が特定のプラットフォームでしか利用できなかったり、あるいはあるライブラリが一部のプラットフォームでしか動かなかったりするケースがこれにあたる。JetBrainsは、このような状況に開発者が柔軟に対応できるよう、優れた仕組みを提供している。
それが「expect/actualメカニズム」と呼ばれるものだ。この仕組みは、共通のコード部分で「こういう機能が必要だ」という期待(expect)を定義しておき、それぞれのプラットフォーム固有のコード部分で「その機能を実際にどう実装するか」(actual)を記述する、というシンプルな構造になっている。例えば、共通のコードで「日付と時刻を取得する」という機能をexpectで定義した場合、AndroidではAndroid用の方法、iOSではiOS用の方法で、それぞれactualとして具体的な実装を提供するわけだ。これにより、共通のロジックを維持しつつ、プラットフォームの特性に合わせて必要な機能を適切に組み込むことが可能になる。プロジェクトでは、このメカニズムを使って、それぞれのプラットフォームに適合するactual関数を必ず用意することが求められる。
しかし、このexpect/actualメカニズムにも一つ課題がある。それは、特定のプラットフォームで動かないライブラリや機能のために、意図しないコードの重複が生じてしまう可能性だ。例えば、GoogleがKMP向けに提供しているandroidxライブラリ群の多くは、モバイルプラットフォーム(AndroidやiOS)やデスクトップ(JVM)を主なターゲットとしており、WebAssembly (Wasm) ターゲットに対応していないことが多い。この場合、Wasm向けには別の実装を用意する必要があるが、iOS、Android、そしてデスクトップの間では共通のactual実装が必要になる。結果として、これら三つのプラットフォームのために同じようなactualコードを繰り返し書くことになり、コードの重複が発生し、保守性の低下やバグの温床になる可能性があるのだ。具体的な例として、共通部分でRevenueCat SDKを使おうとすると、Wasmやデスクトップといった特定のターゲットで「解決できない」というエラーが発生することが挙げられる。
このようなコード重複の問題を解決し、より効率的な開発を実現するための有効な手段が、Gradleのモジュールを工夫して活用することだ。これは、特定のプラットフォームグループで共通して利用できるactual実装を、専用のモジュールとして切り出して管理するというアプローチである。例えば、Room Databaseというデータベースライブラリは、iOS、Android、デスクトップ(JVM)で動作するが、Wasmでは動作しない。この状況に対応するためには、nonWasmModuleと名付けた特別なモジュールを作成し、Wasm以外のこれらのプラットフォームで共有されるactual実装をそこに一箇所にまとめることができる。Wasm向けには、ウェブブラウザのローカルストレージなど、そのプラットフォームに特化した別のデータ保存方法をactualとして実装すればよい。
同様に、iOSとAndroidというモバイルプラットフォームの間でだけ共通する機能やライブラリがある場合、これらをmobileModuleという別のモジュールにまとめて管理することも可能だ。このmobileModuleには、モバイル固有のactual関数やクラス、オブジェクトなどを集約する。最終的に、これらの新しく作成したモジュールが、アプリケーション全体の基盤となる共通モジュール(common module)に依存するように、プロジェクトのGradle設定ファイルを更新する。これにより、各モジュールは共通モジュールの持つ依存関係やコードを継承しつつ、それぞれの役割に応じたプラットフォーム固有のコードを効率的に管理できるのだ。
このようにGradleモジュールを適切に分割することで、コードの重複を大幅に削減し、プロジェクト全体の保守性と拡張性を高めることができる。Compose Multiplatformが掲げる「一度書けばどこでも動く」という理想を追い求めながらも、現実のプラットフォーム間のギャップを埋め、効率的かつ堅牢なアプリケーション開発を進めるためには、expect/actualメカニズムの適切な活用と、今回紹介したようなGradleモジュールによる構造化が非常に有効な手段となるだろう。