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-sample
master
ブランチには、プロジェクトの初期状態であるシンプルな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.shared
Finishをクリックします。ウィザードが共有モジュールを作成し、ビルドスクリプトをそれに応じて変更し、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(): Platform
androidMain/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.Patterns
LoginDataSource
クラスで、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(): String
shared/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アプリのテンプレートを選択し、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を作成できます。
コミュニティリソースも確認できます。