Kotlin Multiplatform プロジェクトの構成を選択する
既存のプロジェクトに Kotlin Multiplatform を追加する場合でも、新しいプロジェクトを開始する場合でも、コードを構造化する方法はいくつかあります。通常は、1 つ以上の Kotlin Multiplatform 共有モジュールを作成し、それらを Android および iOS アプリから使用します。
特定のケースに最適なアプローチを選択するために、以下の質問を検討してください。
- Kotlin Multiplatform モジュールによって生成された iOS フレームワークを、iOS アプリからどのように使用しますか? 直接統合しますか?CocoaPods を介しますか?それとも Swift Package Manager (SPM) を使用しますか?
- Kotlin Multiplatform の共有モジュールは 1 つですか、それとも複数ありますか? 複数の共有モジュールをまとめるアンブレラモジュール(umbrella module)は何にすべきでしょうか?
- すべてのコードをモノレポ(monorepo)に保存しますか、それとも異なるリポジトリに保存しますか?
- Kotlin Multiplatform モジュールのフレームワークを、ローカルまたはリモートの依存関係として使用しますか?
これらの質問に答えることで、プロジェクトに最適な構成を選択できます。
Kotlin Multiplatform モジュールを iOS アプリに接続する
iOS アプリから Kotlin Multiplatform 共有モジュールを使用するには、まずこの共有モジュールから iOS フレームワーク を生成する必要があります。次に、それを iOS プロジェクトの依存関係として追加します。
一般的に、実装方法が異なる 2 つのオプションがあります。
- ローカルの依存関係 (Local dependency): Kotlin のビルドが iOS のビルドと直接やり取りします。
- リモートの依存関係 (Remote dependency): Kotlin のビルドが iOS フレームワークを生成し、それをパッケージマネージャーを使用して iOS プロジェクトに接続します。
iOS 統合に関するすべての利用可能なオプションを確認するには、iOS 統合方法の概要 を参照してください。
モジュール構成
Kotlin Multiplatform プロジェクトで使用できるモジュール構成オプションには、単一モジュールまたは複数の共有モジュールの 2 つがあります。
単一の共有モジュール
最もシンプルなモジュール構成は、プロジェクト内に Kotlin Multiplatform 共有モジュールが 1 つだけ含まれるものです。
Android アプリは、通常の Kotlin モジュールとして Kotlin Multiplatform 共有モジュールに依存できます。しかし、iOS は Kotlin を直接使用できないため、iOS アプリは Kotlin Multiplatform モジュールによって生成された iOS フレームワークに依存する必要があります。
| メリット | デメリット |
|---|---|
|
|
複数の共有モジュール
共有モジュールが成長してきたら、機能(フィーチャー)モジュールに分割することをお勧めします。 これにより、1 つのモジュールしかないことによるスケーラビリティの問題を回避できます。
Android アプリは、すべての機能モジュールに直接依存することも、必要に応じて一部にのみ依存することもできます。
iOS アプリは、Kotlin Multiplatform モジュールによって生成された 1 つのフレームワークに依存できます。複数のモジュールを使用する場合、使用しているすべてのモジュールに依存する追加のモジュール(アンブレラモジュールと呼ばれます)を追加し、すべてのモジュールを含むフレームワーク(アンブレラフレームワークと呼ばれます)を構成する必要があります。
アンブレラフレームワークのバンドルには、プロジェクトのすべての共有モジュールが含まれており、iOS アプリにインポートされます。
| メリット | デメリット |
|---|---|
|
|
アンブレラモジュールをセットアップするには、すべての機能モジュールに依存する個別のモジュールを追加し、このモジュールからフレームワークを生成します。
Android アプリは、一貫性のためにアンブレラモジュールに依存することも、個別の機能モジュールに依存することもできます。アンブレラモジュールには、便利なユーティリティ関数や依存関係注入(DI)のセットアップコードが含まれることがよくあります。
通常、フレームワークのアーティファクトがリモートの依存関係として使用される場合、一部のモジュールのみをアンブレラフレームワークにエクスポートできます。この主な理由は、自動生成されたコードを確実に除外することで、最終的なアーティファクトのサイズを抑えるためです。
アンブレラフレームワーク・アプローチの既知の制約は、iOS アプリが一部の機能モジュールのみを使用することができず、自動的にすべてのモジュールを消費してしまうことです。この機能に関する改善の可能性については、KT-42247 および KT-42250 でご自身のケースを報告してください。
以下の例で iOS アプリがアンブレラモジュールに依存している場合、それはそのモジュールから生成されたアンブレラフレームワークにも依存していることを意味します。
なぜアンブレラフレームワークが必要なのですか?
異なる Kotlin Multiplatform 共有モジュールから生成された複数のフレームワークを iOS アプリに含めることは可能ですが、このアプローチは推奨されません。Kotlin Multiplatform モジュールがフレームワークにコンパイルされると、生成されたフレームワークにはそのすべての依存関係が含まれます。2 つ以上のモジュールが同じ依存関係を使用し、個別のフレームワークとして iOS に公開されるたびに、Kotlin/Native コンパイラは依存関係を重複させます。
この重複は、いくつかの問題を引き起こします。第一に、iOS アプリのサイズが不必要に肥大化します。第二に、ある依存関係のコード構造が、重複した依存関係のコード構造と互換性がなくなります。これにより、iOS アプリケーション内で同じ依存関係を持つ 2 つのモジュールを統合しようとすると問題が発生します。たとえば、異なるモジュールから同じ依存関係を介して渡される状態(State)は接続されません。これは予期しない動作やバグにつながる可能性があります。具体的な制限の詳細については、TouchLab のドキュメント を参照してください。
Kotlin は共通のフレームワーク依存関係を生成しません。生成すると重複が発生し、アプリに追加する Kotlin バイナリは可能な限り小さくする必要があるからです。Kotlin ランタイム全体や、すべての依存関係のすべてのコードを含めることは無駄です。Kotlin コンパイラは、特定のビルドに必要なものだけにバイナリをトリミング(デッドコード削除)できます。しかし、他のビルドが何を必要とするかはわからないため、依存関係を共有しようとすることは不可能です。この問題の影響を最小限に抑えるために、さまざまなオプションを検討しています。
この問題の解決策は、アンブレラフレームワークを使用することです。これにより、重複した依存関係による iOS アプリの肥大化を防ぎ、生成されるアーティファクトを最適化し、依存関係間の非互換性による混乱を排除できます。
リポジトリ構成
新規および既存の Kotlin Multiplatform プロジェクトで使用できるリポジトリ構成オプションがいくつかあります。1 つのリポジトリを使用する方法、または複数のリポジトリを組み合わせる方法があります。
モノレポ: すべてを 1 つのリポジトリに
一般的なリポジトリ構成は、モノレポ(monorepo)構成と呼ばれます。このアプローチは、Kotlin Multiplatform のサンプルやチュートリアルで使用されています。この場合、リポジトリには Android アプリと iOS アプリの両方に加え、共有モジュール、またはアンブレラモジュールを含む複数のモジュールが含まれます。
通常、iOS アプリは直接統合または CocoaPods 統合を使用して、Kotlin Multiplatform 共有モジュールを通常のフレームワークとして使用します。詳細とチュートリアルへのリンクについては、Kotlin Multiplatform モジュールを iOS アプリに接続する を参照してください。
リポジトリがバージョン管理下にある場合、アプリと共有モジュールは同じバージョンを持ちます。
| メリット | デメリット |
|---|---|
|
|
既存の Android アプリと iOS アプリがすでに異なるリポジトリに保存されている場合は、それらをマージする代わりに、Kotlin Multiplatform 部分を Android リポジトリに追加するか、別のリポジトリに追加することができます。
2 つのリポジトリ: Android + 共有 | iOS
もう 1 つのプロジェクト構成は、2 つのリポジトリを持つ方法です。この場合、Kotlin Multiplatform リポジトリには Android アプリと、アンブレラモジュールを含む共有モジュールの両方が含まれ、Xcode プロジェクトには iOS アプリが含まれます。
Android アプリと iOS アプリは個別にバージョン管理でき、共有モジュールは Android アプリとともにバージョン管理されます。
3 つのリポジトリ: Android | iOS | 共有
さらに別のオプションは、Kotlin Multiplatform モジュール専用のリポジトリを持つ方法です。この場合、Android アプリと iOS アプリは個別のリポジトリに保存され、プロジェクトの共有コードには複数の機能モジュールと iOS 用のアンブレラモジュールを含めることができます。
各プロジェクトは個別にバージョン管理できます。Kotlin Multiplatform モジュールも、Android または JVM プラットフォーム用にバージョン管理および公開する必要があります。機能モジュールを個別に公開することも、アンブレラモジュールのみを公開して Android アプリをそれに依存させることもできます。
Android アーティファクトを個別に公開することは、Kotlin Multiplatform モジュールが Android プロジェクトの一部であるシナリオと比較して、Android 開発者にとってさらなる複雑さをもたらす可能性があります。
Android チームと iOS チームの両方が同じバージョン管理されたアーティファクトを使用する場合、両者はバージョンのパリティ(一致)で運用されます。チームの観点からは、共有されている Kotlin Multiplatform コードが Android 開発者によって「所有」されているという印象を避けることができます。機能開発のためにバージョン管理された内部の Kotlin および Swift パッケージをすでに公開している大規模なプロジェクトの場合、共有 Kotlin アーティファクトの公開は既存のワークフローの一部となります。
多数のリポジトリ: Android | iOS | 複数のライブラリ
複数のプラットフォーム上の複数のアプリ間で機能を共有する必要がある場合は、Kotlin Multiplatform コードを含む多数のリポジトリを持つことが好ましい場合があります。たとえば、製品全体で共通のロギングライブラリを、独自のバージョン管理を持つ個別のリポジトリに保存できます。
この場合、複数の Kotlin Multiplatform ライブラリリポジトリを持つことになります。複数の iOS アプリが「ライブラリプロジェクト」の異なるサブセットを使用する場合、各アプリはライブラリプロジェクトへの必要な依存関係を持つアンブレラモジュールを含む追加のリポジトリを持つことができます。
ここでは、各ライブラリも Android または JVM プラットフォーム用にバージョン管理および公開される必要があります。アプリと各ライブラリは個別にバージョン管理できます。
コード共有のワークフロー
iOS アプリは、Kotlin Multiplatform 共有モジュールから生成されたフレームワークを、ローカルまたはリモートの依存関係として使用できます。iOS ビルドでフレームワークへのローカルパスを指定することで、ローカルの依存関係を使用できます。この場合、フレームワークを公開する必要はありません。あるいは、フレームワークを含むアーティファクトをどこかに公開し、他のサードパーティの依存関係と同様に、iOS アプリにリモートの依存関係として使用させることもできます。
ローカル: ソース配布
ローカル配布は、iOS アプリが公開を必要とせずに Kotlin Multiplatform モジュールフレームワークを使用する方法です。iOS アプリは、フレームワークを直接統合するか、CocoaPods を使用して統合できます。
このワークフローは、通常、Android と iOS の両方のチームメンバーが共有の Kotlin Multiplatform コードを編集したい場合に使用されます。iOS 開発者は IntelliJ IDEA または Android Studio をインストールし、Kotlin と Gradle の基礎知識を習得する必要があります。
ローカル配布スキームでは、iOS アプリのビルドが iOS フレームワークの生成をトリガーします。つまり、iOS 開発者は Kotlin Multiplatform コードへの変更をすぐに確認できます。
このシナリオは通常、2 つのケースで使用されます。第一に、アーティファクトを公開する必要のないデフォルトのワークフローとして、モノレポプロジェクト構成で使用できます。第二に、リモートワークフローに加えて、ローカル開発用に使用できます。詳細は ローカル開発用のローカル依存関係のセットアップ を参照してください。
このワークフローは、チームメンバー全員がプロジェクト全体でコードを編集する準備ができている場合に最も効果的です。これには、共通部分を変更した後の Android と iOS の両方の部分が含まれます。理想的には、すべてのチームメンバーが IntelliJ IDEA/Android Studio と Xcode をインストールし、共通コードを変更した後に Android アプリと iOS アプリの両方を開いて実行できる状態であることです。
| メリット | デメリット |
|---|---|
|
|
リモート: アーティファクト配布
リモート配布とは、フレームワークアーティファクトが Swift Package Manager または CocoaPod として公開され、iOS アプリによって使用されることを意味します。Android アプリは、バイナリ依存関係をローカルまたはリモートで使用できます。
リモート配布は、既存のプロジェクトにテクノロジーを徐々に導入するためによく使用されます。これにより、iOS 開発者のワークフローとビルドプロセスが大幅に変更されることはありません。2 つ以上のリポジトリを持つチームは、主にプロジェクトコードを保存するためにリモート配布を使用します。
手始めに、KMMBridge を使用することをお勧めします。これはリモート配布ワークフローを大幅に簡素化するビルドツールセットです。あるいは、同様のワークフローを独自にセットアップすることも可能です。
| メリット | デメリット |
|---|---|
| 参加していない iOS チームメンバーは、Kotlin でコードを書いたり、IntelliJ IDEA/Android Studio や Gradle などのツールの使い方を学んだりする必要がありません。これにより、チームの参入障壁が大幅に下がります。 |
|
ローカル開発用のローカル依存関係のセットアップ
多くのチームは、Kotlin Multiplatform テクノロジーを採用する際、iOS 開発者の開発プロセスを維持するためにリモート配布ワークフローを選択します。しかし、このワークフローでは、彼らが Kotlin Multiplatform コードを変更するのは困難です。Kotlin Multiplatform モジュールから生成されたフレームワークへのローカル依存関係を使用する、追加の「ローカル開発」ワークフローをセットアップすることをお勧めします。
開発者が新しい機能を追加するときは、Kotlin Multiplatform モジュールをローカル의 依存関係として使用するように切り替えます。これにより、共通の Kotlin コードに変更を加え、iOS からその動作をすぐに確認し、Kotlin コードをデバッグできるようになります。機能の準備ができたら、リモートの依存関係に戻し、それに応じて変更を公開できます。まず共有モジュールへの変更を公開し、その後にアプリへの変更を行います。
リモート配布ワークフローには Swift Package Manager を使用します。ローカル配布ワークフローには、フレームワークを直接統合します。
CocoaPods を使用している場合は、ローカル配布ワークフローに CocoaPods を使用することもできます。TouchLab のドキュメント で説明されているように、環境変数を変更することでそれらを切り替えることができます。
