AndroidアプリケーションをiOSで動作させる – チュートリアル
このチュートリアルでは、既存のAndroidアプリケーションをクロスプラットフォーム化し、AndroidとiOSの両方で動作させる方法を説明します。AndroidとiOSの両方のコードを、同じ場所で一度に記述できるようになります。
このチュートリアルでは、ユーザー名とパスワードを入力するためのシングルスクリーンのサンプルAndroidアプリケーションを使用します。認証情報は検証され、メモリ内データベースに保存されます。
アプリケーションをiOSとAndroidの両方で動作させるために、まず一部のコードを共有モジュール(shared module)に移動して、コードをクロスプラットフォーム化します。その後、そのクロスプラットフォームコードをAndroidアプリケーションで使用し、次に同じコードを新しいiOSアプリケーションで使用します。
Kotlinマルチプラットフォームに詳しくない場合は、まずクロスプラットフォームアプリケーションをゼロから作成する方法を学習してください。
開発環境の準備
クイックスタートの指示に従い、Kotlinマルチプラットフォーム開発のための環境構築を完了させてください。
iOSアプリケーションの実行など、このチュートリアルの特定のステップを完了するには、macOSを搭載したMacが必要です。これはAppleの要件によるものです。
Android Studioで、バージョン管理から新しいプロジェクトを作成します:
texthttps://github.com/Kotlin/kmp-integration-samplemasterブランチにはプロジェクトの初期状態(シンプルなAndroidアプリケーション)が含まれています。iOSアプリケーションと共有モジュールを含む最終的な状態を確認するには、finalブランチに切り替えてください。Project ビューに切り替えます:

コードをクロスプラットフォーム化する
コードをクロスプラットフォーム化するために、以下の手順に従います:
- どのコードをクロスプラットフォーム化するか決定する
- クロスプラットフォームコード用の共有モジュールを作成する
- コード共有をテストする
- Androidアプリケーションに共有モジュールへの依存関係を追加する
- ビジネスロジックをクロスプラットフォーム化する
- Androidでクロスプラットフォームアプリケーションを実行する
どのコードをクロスプラットフォーム化するか決定する
AndroidアプリケーションのどのコードをiOSと共有し、どのコードをネイティブのままにするかを決定します。シンプルなルールは、できるだけ再利用したいものを共有することです。ビジネスロジックはAndroidとiOSの両方で同じであることが多いため、再利用の絶好の候補です。
サンプルAndroidアプリケーションでは、ビジネスロジックは com.jetbrains.simplelogin.androidapp.data パッケージに保存されています。将来のiOSアプリケーションでも同じロジックを使用するため、これもクロスプラットフォーム化する必要があります。

クロスプラットフォームコード用の共有モジュールを作成する
iOSとAndroidの両方で使用されるクロスプラットフォームコードは、共有(shared)モジュールに保存されます。Android StudioとIntelliJ IDEAはどちらも、Kotlinマルチプラットフォーム用の共有モジュールを作成するためのウィザードを提供しています。
既存のAndroidアプリケーションと将来のiOSアプリケーションの両方に接続するための共有モジュールを作成します:
Android Studioで、メインメニューから File | New | New Module を選択します。
テンプレートのリストから Kotlin Multiplatform Shared Module を選択します。 ライブラリ名は
sharedのままにし、パッケージ名を入力します:textcom.jetbrains.simplelogin.sharedFinish をクリックします。ウィザードが共有モジュールを作成し、それに応じてビルドスクリプトを変更し、Gradleの同期を開始します。
同期が完了するまで待ちます。
sharedディレクトリに以下のファイル構造が表示されます:
作成されたプロジェクトのレイアウトをより詳しく理解したい場合は、Kotlinマルチプラットフォームプロジェクト構造の基本を参照してください。
sharedモジュールはAndroidアプリケーションのライブラリとして使用されるため、shared/build.gradle.ktsのkotlin.android {}ブロックを以下のandroidLibrary {}ブロックに置き換えます:kotlinimport org.jetbrains.kotlin.gradle.dsl.JvmTarget kotlin { androidLibrary { namespace = "com.jetbrains.simplelogin.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { jvmTarget = JvmTarget.JVM_11 } androidResources { enable = true } withHostTestBuilder { } withDeviceTestBuilder { sourceSetTreeName = "test" }.configure { instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } } //... }
共有モジュールにコードを追加する
共有モジュールが作成されたので、shared/src/commonMain/kotlin/com.jetbrains.simplelogin.shared ディレクトリに共有される共通コードを追加します:
以下のコードで新しい
Greetingクラスを作成します:kotlinpackage com.jetbrains.simplelogin.shared class Greeting { private val platform = getPlatform() fun greet(): String { return "Hello, ${platform.name}!" } }既存のファイルのコードを以下のように置き換えます:
commonMain/Platform.kt内:kotlinpackage com.jetbrains.simplelogin.shared interface Platform { val name: String } expect fun getPlatform(): PlatformandroidMain/Platform.android.kt内:kotlinpackage com.jetbrains.simplelogin.shared import android.os.Build class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" } actual fun getPlatform(): Platform = AndroidPlatform()iosMain/Platform.ios.kt内:kotlinpackage com.jetbrains.simplelogin.shared import platform.UIKit.UIDevice class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } actual fun getPlatform(): Platform = IOSPlatform()
これで、プラットフォーム名をプロパティとして持つプラットフォーム固有のオブジェクトを返す共通の getPlatform() 関数が作成されました。
Androidアプリケーションに共有モジュールへの依存関係を追加する
Androidアプリケーションでクロスプラットフォームコードを使用するには、共有モジュールをアプリケーションに接続し、ビジネスロジックコードをそこに移動して、そのコードをクロスプラットフォーム化します。
app/build.gradle.ktsファイルに共有モジュールへの依存関係を追加します:kotlindependencies { // ... implementation(project(":shared")) }IDEの提案に従うか、File | Sync Project with Gradle Files メニュー項目を使用してGradleファイルを同期します。
app/src/main/java/ディレクトリのcom.jetbrains.simplelogin.androidapp.ui.loginパッケージにあるLoginActivity.ktファイルを開きます。共有モジュールがアプリケーションに正常に接続されていることを確認するために、
onCreate()メソッドにLog.i()呼び出しを追加してgreet()関数の結果をログに出力します:kotlinoverride fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) Log.i("Login Activity", "Hello from shared module: " + (Greeting().greet())) // ... }IDEの提案に従って、不足しているクラスをインポートします。
ツールバーで、実行構成ドロップダウンの隣にあるデバッグアイコンをクリックします:

Logcat ツールウィンドウで、ログから "Hello" を検索すると、共有モジュールからの挨拶が見つかります:

ビジネスロジックをクロスプラットフォーム化する
これで、ビジネスロジックコードをKotlinマルチプラットフォームの共有モジュールの commonMain ソースセットに抽出できます。これにより、AndroidとiOSの両方でコードを使用できるようになります。
ビジネスロジックコード
com.jetbrains.simplelogin.androidapp.dataをappディレクトリからshared/src/commonMainディレクトリのcom.jetbrains.simplelogin.sharedパッケージに移動します。
Android Studioで実行内容を確認されたら、パッケージの移動を選択し、リファクタリングを承認します。

プラットフォーム依存のコードに関する警告はすべて無視し、Refactor Anyway をクリックします。

Android固有のコードをクロスプラットフォームのKotlinコードに置き換えるか、期待される宣言と実際の宣言(expected and actual declarations)を使用してAndroid固有のAPIに接続することで、Android固有のコードを削除します。詳細は以下のセクションを参照してください:
Android固有のコードをクロスプラットフォームコードに置き換える
AndroidとiOSの両方でコードが適切に動作するように、移動した
dataディレクトリ内の可能な限りすべての場所で、JVMの依存関係をKotlinの依存関係に置き換えます。LoginDataValidatorクラスで、android.utilsパッケージのPatternsクラスを、メール検証のパターンに一致するKotlinの正規表現に置き換えます:kotlin// Before private fun isEmailValid(email: String) = Patterns.EMAIL_ADDRESS.matcher(email).matches()kotlin// After private fun isEmailValid(email: String) = emailRegex.matches(email) companion object { private val emailRegex = ("[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + "\\@" + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + "(" + "\\." + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + ")+").toRegex() }Patternsクラスのインポート文を削除します:kotlinimport android.util.PatternsLoginDataSourceクラスで、login()関数のIOExceptionをRuntimeExceptionに置き換えます。IOExceptionは Kotlin/JVM 以外では利用できません。```kotlin // Before return Result.Error(IOException("Error logging in", e)) ``` ```kotlin // After return Result.Error(RuntimeException("Error logging in", e)) ```IOExceptionのインポート文も削除します:kotlinimport java.io.IOException
プラットフォーム固有のUUID生成の実装
LoginDataSourceクラスでは、fakeUser用の汎用一意識別子(UUID)がjava.util.UUIDクラスを使用して生成されていますが、これはiOSでは利用できません。kotlinval fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")Kotlin標準ライブラリはUUID生成用のクラスを提供していますが、ここでは練習のためにプラットフォーム固有の機能を使用してみましょう。
共有コードに
randomUUID()関数のexpect宣言を提供し、AndroidとiOSの各プラットフォームに対応するソースセットにそのactual実装を提供します。詳細はプラットフォーム固有のAPIへの接続で確認できます。login()関数内のjava.util.UUID.randomUUID()呼び出しを、各プラットフォームで実装するrandomUUID()呼び出しに変更します:kotlinval fakeUser = LoggedInUser(randomUUID(), "Jane Doe")shared/src/commonMainディレクトリのcom.jetbrains.simplelogin.sharedパッケージにUtils.ktファイルを作成し、expect宣言を記述します:kotlinpackage com.jetbrains.simplelogin.shared expect fun randomUUID(): Stringshared/src/androidMainディレクトリのcom.jetbrains.simplelogin.sharedパッケージにUtils.android.ktファイルを作成し、AndroidでのrandomUUID()のactual実装を記述します:kotlinpackage com.jetbrains.simplelogin.shared import java.util.* actual fun randomUUID() = UUID.randomUUID().toString()shared/src/iosMainディレクトリのcom.jetbrains.simplelogin.sharedパッケージにUtils.ios.ktファイルを作成し、iOSでのrandomUUID()のactual実装を記述します:kotlinpackage com.jetbrains.simplelogin.shared import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString()shared/src/commonMainディレクトリのLoginDataSource.ktファイルでrandomUUID関数をインポートします:kotlinimport com.jetbrains.simplelogin.shared.randomUUID
これで、KotlinはAndroidとiOSでそれぞれのプラットフォーム固有のUUID実装を使用するようになります。
Androidでクロスプラットフォームアプリケーションを実行する
app 実行構成を実行し、Androidアプリが以前と同じように動作することを確認します。

クロスプラットフォームアプリケーションをiOSで動作させる
Androidアプリケーションをクロスプラットフォーム化したら、iOSアプリケーションを作成し、その中で共有ビジネスロジックを再利用できます。
- XcodeでiOSプロジェクトを作成する
- KMPフレームワークを使用するようにiOSプロジェクトを設定する
- Android StudioでiOSの実行構成をセットアップする
- iOSプロジェクトで共有モジュールを使用する
XcodeでiOSプロジェクトを作成する
Xcodeで、File | New | Project をクリックします。
ダイアログで iOS タブに切り替えます:

App テンプレートを選択し、Next をクリックします。
プロダクト名として "simpleLoginIOS" を指定し、Next をクリックします。

プロジェクトの保存場所として、クロスプラットフォームアプリケーションが保存されているディレクトリ(例:
kmp-integration-sample)を選択します。Android Studioでは、以下の構造になります:

クロスプラットフォームプロジェクトの他のトップレベルディレクトリとの一貫性を保つために、Xcodeを閉じてから
simpleLoginIOSディレクトリをiosAppにリネームします。Xcodeを開いたままフォルダ名を変更すると、警告が表示されたり、プロジェクトが破損したりする可能性があります。

KMPフレームワークを使用するようにiOSプロジェクトを設定する
iOSアプリとKotlinマルチプラットフォームによってビルドされたフレームワーク間の統合を直接セットアップできます。
この方法の代替案(SwiftPM および CocoaPods)については、iOS統合方法の概要で説明されています。
Android Studioで
iosApp/simpleLoginIOS.xcodeprojディレクトリを右クリックし、Open In | Open In Associated Application を選択して、XcodeでiOSプロジェクトを開きます。Xcodeで、Project ナビゲーターのプロジェクト名をクリックして、iOSプロジェクトの設定を開きます。
左側の Targets セクションで simpleLoginIOS を選択し、Build Phases タブをクリックします。
+ アイコンをクリックし、New Run Script Phase を選択します。

実行スクリプト(run script)フィールドに以下のスクリプトを貼り付けます:
bashif [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" exit 0 fi cd "$SRCROOT/.." ./gradlew :shared:embedAndSignAppleFrameworkForXcodeBased on dependency analysis オプションを無効にします。 これにより、Xcodeはビルドのたびにスクリプトを実行し、出力依存関係の欠落に関する警告が毎回出ないようになります。

Run Script フェーズを上に移動し、Compile Sources フェーズの前に配置します:

Build Settings タブの Build Options で、User Script Sandboxing オプションを無効にします:

デフォルトの
DebugまたはReleaseと異なるカスタムビルド構成を使用している場合は、Build Settings タブの User-Defined にKOTLIN_FRAMEWORK_BUILD_TYPE設定を追加し、DebugまたはReleaseに設定してください。Info タブで、カスタムの
CADisableMinimumFrameDurationOnPhoneプロパティを追加し、YESに設定して、iOSでの高リフレッシュレートを有効にします。Signing & Capabilities タブで、開発チームを選択するか、まだ作成していない場合は作成します。これにより、KMPモジュールによって生成された
sharedフレームワークの署名が可能になります。また、ここで Bundle Identifier が一意の値に設定されていることを確認してください。そうでない場合、Xcodeでビルドが失敗する可能性があります。
Xcodeでプロジェクトをビルドします(メインメニューの Product | Build)。 すべてが正しく設定されていれば、プロジェクトのビルドに成功するはずです(「build phase will be run during every build」という警告は無視しても問題ありません)。
User Script Sandboxing オプションを無効にする前にプロジェクトをビルドした場合、ビルドが失敗することがあります。Gradleデーモンプロセスがサンドボックス化されている可能性があるため、再起動が必要です。 プロジェクトディレクトリ(この例では
kmp-integration-sample)で次のコマンドを実行して、プロジェクトを再度ビルドする前に停止させてください:shell./gradlew --stop
Android StudioでiOSの実行構成をセットアップする
Xcodeが正しくセットアップされたことを確認したら、Android Studioに戻ります:
メインメニューで File | Sync Project with Gradle Files を選択します。Android Studioは自動的に simpleLoginIOS という実行構成を生成します。
Android Studioは自動的に simpleLoginIOS という実行構成を生成し、
iosAppディレクトリをリンクされたXcodeプロジェクトとしてマークします。実行構成のリストから simpleLoginIOS を選択します。 iOSエミュレータを選択し、Run をクリックしてiOSの実行構成が正しく動作することを確認します。

iOSプロジェクトで共有モジュールを使用する
shared/build.gradle.kts ファイルは、各iOSターゲットの binaries.framework.baseName プロパティを sharedKit として定義しています。 これが、iOSアプリが利用するためにKotlinマルチプラットフォームがビルドするフレームワークの名前です。
統合をテストするために、Swiftコードから共通コードへの呼び出しを追加します:
Android Studioで
iosApp/simpleloginIOS/ContentView.swiftファイルを開き、フレームワークをインポートします:swiftimport sharedKit適切に接続されているか確認するために、
sharedモジュールのgreet()関数を使用するようにContentView構造体のコードを更新します:swiftstruct ContentView: View { var body: some View { Text(Greeting().greet()) .padding() } }Android StudioのiOS実行構成を使用してアプリを実行し、結果を確認します:

共有モジュールのビジネスロジックを使用してアプリケーションUIをレンダリングするために、
ContentView.swiftファイルのコードを再度更新します:kotlinsimpleLoginIOSApp.swiftファイルでsharedKitモジュールをインポートし、ContentView()関数の引数を指定します:swiftimport SwiftUI import sharedKit @main struct SimpleLoginIOSApp: App { var body: some Scene { WindowGroup { ContentView(viewModel: .init(loginRepository: LoginRepository(dataSource: LoginDataSource()), loginValidator: LoginDataValidator())) } } }iOSの実行構成を再度実行し、iOSアプリにログインフォームが表示されることを確認します。
ユーザー名に "Jane"、パスワードに "password" と入力します。
以前に統合をセットアップしたため、iOSアプリは共通コードを使用して入力を検証します:

結果を楽しむ – ロジックの更新は一度だけ
これで、アプリケーションはクロスプラットフォームになりました。shared モジュールでビジネスロジックを更新すると、AndroidとiOSの両方で結果を確認できます。
ユーザーのパスワードの検証ロジックを変更します。"password" を有効なオプションにしないようにします。 そのためには、
LoginDataValidatorクラスのcheckPassword()関数を更新します (すぐに見つけるには、 を2回押し、クラス名を入力して Classes タブに切り替えます):kotlinpackage com.jetbrains.simplelogin.shared.data class LoginDataValidator { //... fun checkPassword(password: String): Result { return when { password.length < 5 -> Result.Error("Password must be >5 characters") password.lowercase() == "password" -> Result.Error("Password shouldn't be \"password\"") else -> Result.Success } } //... }Android StudioからiOS and Androidの両方のアプリケーションを実行して、変更を確認します (iOSのエラーメッセージは、赤い警告の三角形をタップすると表示されます):

このチュートリアルの最終的なコードを確認できます。
他に何を共有できるか?
アプリケーションのビジネスロジックを共有しましたが、アプリケーションの他のレイヤーを共有することも決定できます。 たとえば、ViewModel クラスのコードは Android と iOSアプリケーション でほぼ同じであり、モバイルアプリケーションが同じプレゼンテーションレイヤーを持つ必要がある場合は、それを共有できます。
次のステップ
Androidアプリケーションをクロスプラットフォーム化した後は、以下に進むことができます:
Compose Multiplatformを使用して、すべてのプラットフォームで統合されたUIを作成できます:
- Compose MultiplatformとJetpack Composeについて学ぶ
- Compose Multiplatformで利用可能なリソースを探索する
- 共有ロジックとUIを備えたアプリを作成する
コミュニティのリソースもチェックしてみてください:
