Kotlin マルチプラットフォームを使ってみる

1. 始める前に

前提条件

  • Jetpack Compose アプリを構築する方法に関する知識
  • Kotlin の使用経験。
  • Swift 構文に関する基本的な知識。

必要なもの

学習内容

  • Kotlin マルチプラットフォームの基本を理解する。
  • プラットフォーム間でコードを共有する方法。
  • Android と iOS で共有コードを接続する方法。

2. セットアップする

バックアップの手順:

  1. GitHub リポジトリのクローンを作成します。
$ git clone https://github.com/android/codelab-android-kmp.git

または、リポジトリを ZIP ファイルとしてダウンロードすることもできます。

  1. Android Studioget-started プロジェクトを開きます。このプロジェクトには以下のブランチが含まれます。
  • main: このプロジェクトのスターター コードが含まれています。これに変更を加えて Codelab を完了します。
  • end: この Codelab の解答コードが含まれています。

この Codelab では、main ブランチから始めます。Codelab の手順に沿って、自分のペースで進めることができます。

  1. 解答コードを確認する場合は、このコマンドを実行します。
$ git clone -b end https://github.com/android/codelab-android-kmp.git

または、解答コードをダウンロードすることもできます。

XCode をインストールします。

この Codelab の iOS 部分をビルドして実行するには、Xcode と iOS シミュレータが必要です。

  1. Mac App Store から Xcode をインストールします(Apple アカウントが必要です)。
  2. インストールが完了したら、Xcode を起動します。
  3. 組み込まれているコンポーネントと、ダウンロードが必要なコンポーネントを示すダイアログが表示されます。c4aba94d795dabee.png
  4. iOS 18.4(またはそれ以降)を確認します。
  5. [ダウンロードしてインストール] をクリックします。
  6. コンポーネントがインストールされるまで待ちます。

この Codelab は Xcode 16.3 でテストされています。他のバージョンの Xcode を使用しているときに問題が発生した場合は、この Codelab で説明されているバージョンをダウンロードすることをおすすめします。

サンプルアプリ

このコードベースには、Jetpack Compose でビルドされた Android アプリと SwiftUI でビルドされた iOS アプリの両方が含まれています。Android プロジェクトは androidApp/ フォルダに配置されます。iOS プロジェクトは iosApp/ フォルダに配置され、このフォルダには、Xcode で実行する KMPGetStartedCodelab.xcodeproj も含まれています。

3. Kotlin マルチプラットフォームの概要

Kotlin マルチプラットフォーム(KMP)を使用すると、コードを 1 回記述して、Android、iOS、ウェブ、デスクトップなどの複数のターゲット プラットフォームで共有できます。KMP を活用することで、コードの重複を最小限に抑え、整合性を維持し、開発時間と労力を大幅に削減できます。

KMP では、共有するコードベースの量や部分を判断しません。共有する価値があるコードの部分は、ご自身で判断してください。

共有する内容を決める

この共有コードにより、プラットフォーム間で整合性を維持し、重複を減らすことができます。多くのモバイル チームは、まず個別のビジネス ロジック(データベース アクセス、ネットワーク アクセスなど)と関連するテストを共有し、その後、追加のコードを共有します。

多くの Android Jetpack ライブラリは KMP をサポートしています。マルチプラットフォーム化された Jetpack ライブラリは、ターゲット プラットフォームに応じて複数のサポート ティアを提供します。ライブラリとそのサポートレベルの一覧については、ドキュメントをご覧ください。

たとえば、サポートされているライブラリの 1 つである Room データベース ライブラリは、Android、iOS、デスクトップのすべてをサポートしています。これにより、両方のプラットフォームの他のネイティブ コードを保持しながら、データベースの作成と関連するデータベース ロジックを共通の KMP 共有モジュールに移植できます。

データベースの移行後の次のステップとして、他のドメイン ロジックを共有することもできます。また、Android Jetpack の ViewModel マルチプラットフォーム ライブラリの使用も検討してください。

プラットフォーム固有のコードを記述する方法

Kotlin マルチプラットフォームでは、プラットフォーム固有の機能を実装するための新しい手法が導入されています。

expect 宣言と actual 宣言

expect actual Kotlin 言語機能は、IDE を完全にサポートする Kotlin マルチプラットフォーム コードベースをサポートするように設計されています。

このアプローチは、プラットフォーム固有の動作を 1 つの関数またはクラスにカプセル化できる場合に最適です。これは柔軟で強力なメカニズムです。たとえば、共通の expect クラスには、よりオープンな可視性修飾子、追加のスーパータイプ、異なるパラメータ タイプまたは修飾子を持つ、プラットフォーム固有の actual の対応クラスを設定できます。厳格なインターフェース API では、このようなバリエーションを実現できません。さらに、expect actual は静的に解決されます。つまり、プラットフォーム固有の実装がコンパイル時に適用されます。

Kotlin のインターフェースと実装

両方のプラットフォームで同様の API に準拠しつつ、実装が異なる必要がある場合は、expect 宣言とactual 宣言の代わりに、共有コードでインターフェースを定義できます。このアプローチにより、異なるテスト実装を使用したり、実行時に別の実装に切り替えたりできます。

また、インターフェースには Kotlin 固有の知識は必要ないため、他の言語のインターフェースに精通しているデベロッパーにも使いやすいオプションです。

共通の共有コードのインターフェース、ネイティブ コード(Android または Swift)の実装。

場合によっては、KMP コードでは利用できないコードを記述する必要があります。この場合、共有コードでインターフェースを定義し、Android 用に Kotlin で実装し、iOS 用に Swift で対応するインターフェースを用意できます。通常、ネイティブ実装は、依存関係インジェクションまたは直接インジェクションによって共有コードに挿入されます。この戦略により、共有コードの共通インターフェースを維持しながら、各プラットフォームでカスタマイズされたエクスペリエンスを実現できます。

4. Android Studio から Xcode プロジェクトを開きます。

Xcode をインストールしたら、iOS アプリを実行できることを確認する必要があります。

iOS プロジェクトは Android Studio から直接開くことができます。

  1. [Project] ペインを切り替えて Project ビューを使用します。4f1a2c2ad988334c.png
  2. [root]/iosApp/ フォルダ 1357ffb58fe05180.pngKmpGetStartedCodelab.xcodeproj ファイルを探します。
  3. ファイルを右クリックし、[開く]、[関連するアプリで開く] の順に選択します。Xcode で iOS アプリが開きます。f93dee415dfd40e9.png
  4. ⌘+R をクリックするか、[Product] メニューに移動して [Run] を選択して、Xcode でプロジェクトを実行します。

fff7f322c13ccdc0.png

5. KMP モジュールを追加する

プロジェクトに KMP のサポートを追加するには、まず、プラットフォーム(Android、iOS)間で再利用されるコードの shared モジュールを作成します。

Android Studio には、KMP 共有モジュール テンプレートを使用して Kotlin マルチプラットフォーム モジュールを追加する方法が用意されています。

KMP モジュールを作成するには、Android Studio で次の操作を行います。

  1. [File] > [New] > [New Module] > [Kotlin Multiplatform Shared Module] に移動します。
  2. パッケージを com.example.kmp.shared に変更します。
  3. [Finish] 4ef605dcc1fe6301.png をクリックします。
  4. モジュールの作成が完了し、Gradle の同期が完了すると、プロジェクトに新しい shared モジュールが表示されます。下記のビューを表示するには、[Android] ビューから [Project] ビューに切り替える必要がある場合があります。

eb58eea4e534dab2.png

KMP 共有モジュール テンプレートによって生成された共有モジュールには、基本的なプレースホルダ関数とテストが含まれています。これらのプレースホルダにより、モジュールが最初から正常にコンパイルされ、実行されます。

重要: iosApp フォルダと iosMain フォルダの違いに注意してください。iosApp フォルダにはスタンドアロンの iOS アプリコードが含まれ、iosMain は追加した KMP 共有モジュールの一部です。iosApp には Swift コードが含まれ、iosMain には iOS プラットフォーム固有の KMP コードが含まれます。

まず、アプリが共有コードを使用できるように、新しい共有モジュールを :androidApp Gradle モジュールの依存関係としてリンクする必要があります。

  1. androidApp/build.gradle.kts ファイルを開きます。
  2. 次のように、依存関係ブロックに shared モジュールの依存関係を追加します。
dependencies {
    ...
    implementation(projects.shared)
}
  1. プロジェクトを Gradle ファイルと同期します。c4a6ca72cf444e6e.png

shared モジュールへのコードアクセスを確認する

Android アプリが shared モジュールのコードにアクセスできることを確認するため、アプリを簡単に更新します。

  1. KMPGetStartedCodelab プロジェクトで、androidApp/src/main/java/com/example/kmp/getstarted/android/MainActivity.kt にある MainActivity ファイルを開きます。
  2. Text コンポーザブルのコンテンツを変更して、表示される文字列に platform() 情報を含めます。
Text(
  "Hello ${platform()}",
)
  1. キーボードの ⌥(option)+return をクリックし、Import function 'platform'da301d17884eaef9.png を選択します。
  2. アプリをビルドして、Android デバイスまたはエミュレータで実行します。

この更新により、アプリが shared モジュールから platform() 関数を呼び出せるかどうかが確認されます。Android プラットフォームで実行すると、"Android" が返されます。

9828afe63d7cd9da.png

6. iOS アプリに共有モジュールを設定する

Swift は、Android アプリのように Kotlin モジュールを直接使用できないため、コンパイル済みバイナリ フレームワーク(XCFramework バンドル)を生成する必要があります。XCFramework バンドルは、複数の Apple プラットフォーム用にビルドするために必要なフレームワークとライブラリを含むバイナリ パッケージです。

共有ライブラリの配布方法

Android Studio の新しいモジュール テンプレートでは、各 iOS アーキテクチャのフレームワークを生成するように共有モジュールがすでに構成されています。次のコードは、shared モジュールの build.gradle.kts ファイルにあります。

val xcfName = "sharedKit"

iosX64 {
  binaries.framework {
    baseName = xcfName
  }
}

iosArm64 {
  binaries.framework {
    baseName = xcfName
  }
}

iosSimulatorArm64 {
  binaries.framework {
    baseName = xcfName
  }
}

このステップでは、スクリプトを実行して Kotlin フレームワークを生成するように Xcode を設定し、iOS アプリで platform() 関数を呼び出します。

共有ライブラリを使用するには、次の手順で Kotlin フレームワークを iOS プロジェクトに接続する必要があります。

  1. Xcode で iOS プロジェクト(前述の iosApp ディレクトリ)を開き、プロジェクト ナビゲータ 94047b06db4a3b6f.png でプロジェクト名をダブルクリックしてプロジェクト設定を開きます。
  2. プロジェクト設定の [Build Phases] タブで、+ をクリックして [New Run Script Phase] を選択します。これにより、他のすべてのフェーズの後に新しい「実行スクリプト」フェーズが追加されます。d4907a9cb0a5ac6e.png
  3. [Run Script] のタイトルをダブルクリックして名前を変更します。このフェーズの内容を明確にするために、デフォルトの [Run Script] という名前を [Compile Kotlin Framework] に変更します。
  4. ビルドフェーズを開き、[Shell] の下のテキスト フィールドに次のスクリプト コードを入力します。
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

7b6a393e44ddbe60.png

  1. [Compile Kotlin Framework] フェーズを [Compile Sources] フェーズのにドラッグします。27dbe2cf958c986f.png
  2. ⌘+B をクリックするか、プロダクト メニューに移動して [ビルド] を選択して、Xcode でプロジェクトをビルドします。ビルドの進行状況は Xcode の上部に表示されます。bb0f9cb0f96d1f89.png

すべてが正しく設定されていれば、プロジェクトは正常にビルドされます。

bb9b12d5f6ad0bac.png

このように実行スクリプト ビルドフェーズを設定すると、共有モジュールをコンパイルするために別のツールに切り替えることなく、Xcode から iOS プロジェクトをコンパイルできます。

shared モジュールへのコードアクセスを確認する

iOS アプリが shared モジュールのコードに正常にアクセスできることを確認するには、Android アプリに対して行った簡単な更新を iOS アプリに対して行います。

  1. Xcode の iOS プロジェクトで、Sources/View/ContentView.swift にある ContentView.swift ファイルを開きます。
  2. ファイルの先頭に import sharedKit を追加します。
  3. Text ビューを変更して、表示される文字列に Platform_iosKt.platform() 情報を \(Platform_iosKt.platform()) で含めます。

ファイルの最終的な結果は次のようになります。

import SwiftUI
import sharedKit 

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            
            Text("Hello, \(Platform_iosKt.platform())!")
        }
        .padding()
    }
}
  1. ⌘+R をクリックするか、[Product] メニューに移動して [Run] をクリックしてアプリを実行します。

このアップデートでは、iOS アプリが共有モジュールから platform() 関数を呼び出せるかどうかを確認します。iOS プラットフォームで実行すると "iOS" が返されます。

8024b5cc4bcd3f84.png

7. Swift/Kotlin インターフェース エンハンサ(SKIE)を追加

デフォルトでは、Kotlin が生成したネイティブ インターフェースは Objective-C ヘッダーです。Swift は Objective-C と直接互換性がありますが、Objective-C には Swift や Kotlin の最新機能がすべて含まれているわけではありません。

これは、前の例で Swift コードで platform() 呼び出しを直接使用できなかった理由でもあります。Objective-C はグローバル関数をサポートしておらず、クラスにカプセル化された静的関数のみをサポートしているため、KMP はグローバル関数を生成できません。そのため、Platform_iosKt を追加する必要があります。

インターフェースを Swift に適したものにするには、Swift/Kotlin Interface Enhancer(SKIE)ツールを使用して、:shared モジュールの Swift インターフェースを改善します。

SKIE の一般的な機能は次のとおりです。

  • デフォルト引数のサポートの強化
  • sealed 階層のサポートを強化(sealed classsealed interface
  • switch ステートメントでの網羅的な処理を備えた enum class のサポートの改善
  • FlowAsyncSequence の互換性
  • suspend funasync func の互換性
  1. co.touchlab.skie Gradle プラグインを libs.versions.toml ファイルに追加します。
[versions]
skie = "0.10.1"

[plugins]
skie = { id = "co.touchlab.skie", version.ref = "skie" }
  1. プラグインをルートの build.gradle.kts ファイルに追加する
plugins {
   ...
   alias(libs.plugins.skie) apply false
}
  1. プラグインを :shared モジュールの build.gradle.kts ファイルに追加します。
plugins {
   ...
   alias(libs.plugins.skie)
}
  1. Gradle がプロジェクトを同期する

静的関数呼び出しを削除する

iOS アプリを再ビルドしてもすぐには何も変化しませんが、Platform_iosKt 接頭辞を削除して、platform() 関数をグローバル関数として機能させることができます。

Text("Hello, KMP! \(platform())")

これは、SKIE が(他の機能とともに)Swift の API Notesを活用しているためです。API Notes は、Swift コードから API をより適切に使用できるように、API に関する情報を追加します。

8. 完了

これで、Android プロジェクトと iOS プロジェクトに最初の共有 Kotlin マルチプラットフォーム コードが追加されました。これは最小限の開始点にすぎませんが、これで KMP でコードを共有するための高度な機能とユースケースを探求できるようになります。

次のステップ

次の codelab では、Jetpack Room を使用して Android と iOS 間でデータレイヤを共有する方法を学びます。

詳細