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 Studio で、get-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. まだ作成されていない場合は、Apple アカウントを作成することが必要である可能性があります。
  2. Mac アプリストアから Xcode をインストールします(Apple アカウントが必要です)。
  3. インストールが完了したら、Xcode を起動します。
  4. 組み込まれているコンポーネントと、ダウンロードが必要なコンポーネントを示すダイアログが表示されます。c4aba94d795dabee.png
  5. iOS 18.4(またはより新しいバージョン)を確認します。
  6. [ダウンロードしてインストール] をクリックします。
  7. コンポーネントがインストールされるまで待ちます。

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

サンプルアプリ

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

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

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

KMP では、共有するコードベースの量や部分を判断しません。共有する価値があるコードの部分は、自由に決定できます。

共有する内容を決定する

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

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

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

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

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

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

expect 宣言と actual 宣言

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

このアプローチは、プラットフォーム固有の動作を単一の関数またはクラスにカプセル化できる場合に最適です。これは柔軟かつパワフルなメカニズムです。たとえば、共通の 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. KmpGetStartedCodelab.xcodeproj フォルダ 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.ktMainActivity ファイルを開きます。
  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. プロジェクト設定の [ビルドフェーズ] タブで、+ をクリックして [新しいスクリプトの実行フェーズ] を選択します。これにより、他のすべてのフェーズの後ろに新しい「スクリプトの実行」フェーズが追加されます。d4907a9cb0a5ac6e.png
  3. [スクリプトの実行] のタイトルをダブルクリックして名称を変更します。このフェーズの内容を明確にするために、デフォルトの [スクリプトの実行] の名称を [Kotlin フレームワークのコンパイル] に変更します。
  4. ビルドフェーズを開き、[Shell] の下のテキスト フィールドに次のスクリプト コードを入力します。
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

7b6a393e44ddbe60.png

  1. [ Kotlin フレームワークのコンパイル] フェーズを [ソースのコンパイル] フェーズのにドラッグします。27dbe2cf958c986f.png
  2. ⌘+B をクリックするか、[Product] メニューに移動して [ビルド] を選択し、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 Interface Enhancer(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 間でデータレイヤを共有することについて学びます。

詳細