AndroidアプリケーションをiOSで動作させる – チュートリアル
このチュートリアルではAndroid Studioを使用しますが、IntelliJ IDEAでも進めることができます。
このチュートリアルでは、既存のAndroidアプリケーションをAndroidとiOSの両方で動作するクロスプラットフォームアプリケーションにする方法を説明します。 これにより、AndroidとiOS両方のコードを一度に、同じ場所で記述できるようになります。
このチュートリアルでは、ユーザー名とパスワードを入力する単一画面を持つサンプルAndroidアプリケーションを使用します。入力された認証情報は検証され、インメモリデータベースに保存されます。
アプリケーションをiOSとAndroidの両方で動作させるには、 まず、コードの一部を共有モジュールに移動してクロスプラットフォーム化します。 その後、Androidアプリケーションでそのクロスプラットフォームコードを使用し、さらに新しいiOSアプリケーションでも同じコードを使用します。
Kotlin Multiplatformに不慣れな場合は、まずゼロからクロスプラットフォームアプリケーションを作成する方法を学習してください。
開発環境の準備
クイックスタートで、Kotlin Multiplatform開発の環境設定の手順を完了します。
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の両方で使用されるクロスプラットフォームコードは、共有モジュールに保存されます。 Android StudioとIntelliJ IDEAの両方に、Kotlin Multiplatform用の共有モジュールを作成するウィザードが用意されています。
既存のAndroidアプリケーションと将来のiOSアプリケーションの両方に接続するための共有モジュールを作成します。
Android Studioで、メインメニューからFile | New | New Moduleを選択します。
テンプレートのリストからKotlin Multiplatform Shared Moduleを選択します。 ライブラリ名は
sharedのままにし、パッケージ名を入力します。textcom.jetbrains.simplelogin.sharedFinishをクリックします。ウィザードが共有モジュールを作成し、ビルドスクリプトをそれに応じて変更し、Gradle同期を開始します。
セットアップが完了すると、
sharedディレクトリに次のファイル構造が表示されます。
shared/build.gradle.ktsファイル内のkotlin.androidLibrary.minSdkプロパティの値が、app/build.gradle.ktsファイル内の同じプロパティの値と一致していることを確認します。
共有モジュールにコードを追加する
共有モジュールが作成できたので、 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()
作成されるプロジェクトのレイアウトについてより深く理解したい場合は、 Kotlin Multiplatformプロジェクト構造の基本を参照してください。
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?) { super.onCreate(savedInstanceState) Log.i("Login Activity", "Hello from shared module: " + (Greeting().greet())) // ... }IDEの提案に従って、不足しているクラスをインポートします。
ツールバーで、
appドロップダウンをクリックし、デバッグアイコンをクリックします。
Logcatツールウィンドウでログから「Hello」を検索すると、共有モジュールからの挨拶が見つかります。

ビジネスロジックをクロスプラットフォーム化する
これで、ビジネスロジックコードをKotlin Multiplatform共有モジュールに抽出し、プラットフォーム非依存にすることができます。 これは、AndroidとiOSの両方でコードを再利用するために必要です。
ビジネスロジックコード
com.jetbrains.simplelogin.androidapp.dataをappディレクトリからshared/src/commonMainディレクトリ内のcom.jetbrains.simplelogin.sharedパッケージに移動します。
Android Studioが何をしたいか尋ねてきたら、パッケージを移動することを選択し、リファクタリングを承認します。

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

Android固有のコードを、クロスプラットフォームのKotlinコードに置き換えるか、expectとactual宣言を使用してAndroid固有のAPIに接続することで削除します。詳細については、以下のセクションを参照してください。
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クラスのimportディレクティブを削除します。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のimportディレクティブも削除します。kotlinimport java.io.IOException
クロスプラットフォームコードからプラットフォーム固有のAPIに接続する
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で実行する
クロスプラットフォームアプリケーションを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では、以下の構造が得られます。

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

KMPフレームワークを使用するようにiOSプロジェクトを設定する
iOSアプリとKotlin Multiplatformによってビルドされたフレームワーク間の統合を直接設定できます。 この方法以外の代替手段については、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を選択します。

ランスクリプトフィールドに以下のスクリプトを貼り付けます。
textcd "$SRCROOT/.." ./gradlew :shared:embedAndSignAppleFrameworkForXcode
Based 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に設定します。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として定義しています。 これは、Kotlin MultiplatformがiOSアプリが利用するためにビルドするフレームワークの名前です。
統合をテストするために、Swiftコードで共通コードを呼び出します。
Android Studioで、
iosApp/simpleloginIOS/ContentView.swiftファイルを開き、フレームワークをインポートします。swiftimport sharedKit正しく接続されていることを確認するには、
ContentView構造をクロスプラットフォームアプリの共有モジュールからgreet()関数を使用するように変更します。swiftstruct ContentView: View { var body: some View { Text(Greeting().greet()) .padding() } }Android StudioのiOS実行構成を使用してアプリを実行し、結果を確認します。

ContentView.swiftファイルのコードを再度更新し、共有モジュールのビジネスロジックを使用してアプリケーションUIをレンダリングします。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およびAndroidアプリケーションの両方を実行し、変更を確認します。

このチュートリアルの最終コードを確認できます。
他に共有できるものは?
アプリケーションのビジネスロジックを共有しましたが、アプリケーションの他のレイヤーも共有することに決定できます。 たとえば、ViewModelクラスのコードはAndroidとiOSアプリケーションでほとんど同じであり、モバイルアプリケーションが同じプレゼンテーション層を持つべきであれば、それを共有できます。
次のステップ
Androidアプリケーションをクロスプラットフォーム化した後、さらに次のことができます。
Compose Multiplatformを使用して、すべてのプラットフォームで統一されたUIを作成できます。
コミュニティリソースも確認できます。
