Skip to content

AndroidアプリケーションをiOSで動作させる – チュートリアル

このチュートリアルではAndroid Studioを使用しますが、IntelliJ IDEAでも進めることができます。

されていれば、両方のIDEは同じコア機能とKotlin Multiplatformのサポートを共有します。

このチュートリアルでは、既存のAndroidアプリケーションをAndroidとiOSの両方で動作するクロスプラットフォームアプリケーションにする方法を説明します。 これにより、AndroidとiOS両方のコードを一度に、同じ場所で記述できるようになります。

このチュートリアルでは、ユーザー名とパスワードを入力する単一画面を持つサンプルAndroidアプリケーションを使用します。入力された認証情報は検証され、インメモリデータベースに保存されます。

アプリケーションをiOSとAndroidの両方で動作させるには、 まず、コードの一部を共有モジュールに移動してクロスプラットフォーム化します。 その後、Androidアプリケーションでそのクロスプラットフォームコードを使用し、さらに新しいiOSアプリケーションでも同じコードを使用します。

Kotlin Multiplatformに不慣れな場合は、まずゼロからクロスプラットフォームアプリケーションを作成する方法を学習してください。

開発環境の準備

  1. クイックスタートで、Kotlin Multiplatform開発の環境設定の手順を完了します。

    iOSアプリケーションの実行など、このチュートリアルの一部の手順を完了するには、macOSがインストールされたMacが必要です。 これはAppleの要件によるものです。

  2. Android Studioで、バージョン管理から新しいプロジェクトを作成します。

    text
    https://github.com/Kotlin/kmp-integration-sample

    masterブランチには、プロジェクトの初期状態であるシンプルなAndroidアプリケーションが含まれています。 iOSアプリケーションと共有モジュールを含む最終状態を確認するには、finalブランチに切り替えてください。

  3. Projectビューに切り替えます。

    プロジェクトビュー

コードをクロスプラットフォーム化する

コードをクロスプラットフォーム化するには、次の手順を実行します。

  1. どのコードをクロスプラットフォームにするか決定する
  2. クロスプラットフォームコード用の共有モジュールを作成する
  3. コード共有をテストする
  4. Androidアプリケーションに共有モジュールへの依存関係を追加する
  5. ビジネスロジックをクロスプラットフォーム化する
  6. クロスプラットフォームアプリケーションをAndroidで実行する

どのコードをクロスプラットフォームにするか決定する

AndroidアプリケーションのどのコードをiOSと共有し、どのコードをネイティブとして保持するかを決定します。シンプルなルールは、 可能な限り再利用したいものを共有するというものです。ビジネスロジックはAndroidとiOSの両方で同じであることが多いため、 再利用の有力な候補となります。

サンプルAndroidアプリケーションでは、ビジネスロジックはcom.jetbrains.simplelogin.androidapp.dataパッケージに保存されています。 将来のiOSアプリケーションも同じロジックを使用するため、これもクロスプラットフォーム化する必要があります。

共有するビジネスロジック

クロスプラットフォームコード用の共有モジュールを作成する

iOSとAndroidの両方で使用されるクロスプラットフォームコードは、共有モジュールに保存されます。 Android StudioとIntelliJ IDEAの両方に、Kotlin Multiplatform用の共有モジュールを作成するウィザードが用意されています。

既存のAndroidアプリケーションと将来のiOSアプリケーションの両方に接続するための共有モジュールを作成します。

  1. Android Studioで、メインメニューからFile | New | New Moduleを選択します。

  2. テンプレートのリストからKotlin Multiplatform Shared Moduleを選択します。 ライブラリ名はsharedのままにし、パッケージ名を入力します。

    text
    com.jetbrains.simplelogin.shared
  3. Finishをクリックします。ウィザードが共有モジュールを作成し、ビルドスクリプトをそれに応じて変更し、Gradle同期を開始します。

  4. セットアップが完了すると、sharedディレクトリに次のファイル構造が表示されます。

    sharedディレクトリ内の最終ファイル構造

  5. shared/build.gradle.ktsファイル内のkotlin.androidLibrary.minSdkプロパティの値が、app/build.gradle.ktsファイル内の同じプロパティの値と一致していることを確認します。

共有モジュールにコードを追加する

共有モジュールが作成できたので、 commonMain/kotlin/com.jetbrains.simplelogin.sharedディレクトリに共有する共通コードを追加します。

  1. 次のコードで新しいGreetingクラスを作成します。

    kotlin
    package com.jetbrains.simplelogin.shared
    
    class Greeting {
        private val platform = getPlatform()
    
        fun greet(): String {
            return "Hello, ${platform.name}!"
        }
    }
  2. 作成されたファイルのコードを次のように置き換えます。

    • commonMain/Platform.ktで:

      kotlin
      package com.jetbrains.simplelogin.shared
      
      interface Platform {
          val name: String
      }
      
      expect fun getPlatform(): Platform
    • androidMain/Platform.android.ktで:

      kotlin
      package 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で:

      kotlin
      package 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アプリケーションでクロスプラットフォームコードを使用するには、共有モジュールを接続し、ビジネスロジックコードをそこに移動して、このコードをクロスプラットフォームにします。

  1. app/build.gradle.ktsファイルに共有モジュールへの依存関係を追加します。

    kotlin
    dependencies {
        // ...
        implementation(project(":shared"))
    }
  2. IDEの提案に従うか、File | Sync Project with Gradle Filesメニュー項目を使用してGradleファイルを同期します。

  3. app/src/main/java/ディレクトリで、com.jetbrains.simplelogin.androidapp.ui.loginパッケージ内のLoginActivity.ktファイルを開きます。

  4. 共有モジュールがアプリケーションに正常に接続されていることを確認するには、onCreate()メソッドにLog.i()呼び出しを追加して、greet()関数の結果をログに出力します。

    kotlin
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        Log.i("Login Activity", "Hello from shared module: " + (Greeting().greet()))
    
        // ...
    }
  5. IDEの提案に従って、不足しているクラスをインポートします。

  6. ツールバーで、appドロップダウンをクリックし、デバッグアイコンをクリックします。

    デバッグするアプリのリスト

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

    共有モジュールからの挨拶

ビジネスロジックをクロスプラットフォーム化する

これで、ビジネスロジックコードをKotlin Multiplatform共有モジュールに抽出し、プラットフォーム非依存にすることができます。 これは、AndroidとiOSの両方でコードを再利用するために必要です。

  1. ビジネスロジックコードcom.jetbrains.simplelogin.androidapp.dataappディレクトリから shared/src/commonMainディレクトリ内のcom.jetbrains.simplelogin.sharedパッケージに移動します。

    ビジネスロジックコードを含むパッケージをドラッグアンドドロップ

  2. Android Studioが何をしたいか尋ねてきたら、パッケージを移動することを選択し、リファクタリングを承認します。

    ビジネスロジックパッケージをリファクタリング

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

    プラットフォーム依存コードに関する警告

  4. Android固有のコードを、クロスプラットフォームのKotlinコードに置き換えるか、expectとactual宣言を使用してAndroid固有のAPIに接続することで削除します。詳細については、以下のセクションを参照してください。

    Android固有のコードをクロスプラットフォームコードに置き換える

    コードをAndroidとiOSの両方でうまく動作させるには、移動したdataディレクトリ内で、可能な限りすべてのJVM依存関係をKotlinの依存関係に置き換えます。

    1. 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()
      }
    2. Patternsクラスのimportディレクティブを削除します。

      kotlin
      import android.util.Patterns
    3. LoginDataSourceクラスで、login()関数内のIOExceptionRuntimeExceptionに置き換えます。 IOExceptionはKotlin/JVMでは利用できません。

      ```kotlin
      // Before
      return Result.Error(IOException("Error logging in", e))
      ```
      
      ```kotlin
      // After
      return Result.Error(RuntimeException("Error logging in", e))
      ```
      
    4. IOExceptionのimportディレクティブも削除します。

      kotlin
      import java.io.IOException

    クロスプラットフォームコードからプラットフォーム固有のAPIに接続する

    LoginDataSourceクラスでは、fakeUserの汎用一意識別子(UUID)が java.util.UUIDクラスを使用して生成されますが、これはiOSでは利用できません。

    kotlin
    val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")

    Kotlin標準ライブラリにはUUID生成のための実験的なクラスが提供されていますが、 ここではその練習のためにプラットフォーム固有の機能を使用してみましょう。

    共有コードでrandomUUID()関数のexpect宣言を提供し、対応するソースセットで 各プラットフォーム(AndroidとiOS)向けのactual実装を提供します。 プラットフォーム固有のAPIへの接続について詳しく学ぶことができます。

    1. login()関数内のjava.util.UUID.randomUUID()呼び出しを、各プラットフォーム向けに実装するrandomUUID()呼び出しに変更します。

      kotlin
      val fakeUser = LoggedInUser(randomUUID(), "Jane Doe")
    2. shared/src/commonMainディレクトリのcom.jetbrains.simplelogin.sharedパッケージにUtils.ktファイルを作成し、expect宣言を提供します。

      kotlin
      package com.jetbrains.simplelogin.shared
      
      expect fun randomUUID(): String
    3. shared/src/androidMainディレクトリのcom.jetbrains.simplelogin.sharedパッケージにUtils.android.ktファイルを作成し、AndroidでのrandomUUID()actual実装を提供します。

      kotlin
      package com.jetbrains.simplelogin.shared
      
      import java.util.*
      
      actual fun randomUUID() = UUID.randomUUID().toString()
    4. shared/src/iosMainディレクトリのcom.jetbrains.simplelogin.sharedUtils.ios.ktファイルを作成し、 iOSでのrandomUUID()actual実装を提供します。

      kotlin
      package com.jetbrains.simplelogin.shared
      
      import platform.Foundation.NSUUID
      
      actual fun randomUUID(): String = NSUUID().UUIDString()
    5. shared/src/commonMainディレクトリのLoginDataSource.ktファイルでrandomUUID関数をインポートします。

      kotlin
      import com.jetbrains.simplelogin.shared.randomUUID

これで、KotlinはAndroidとiOSに対して、プラットフォーム固有のUUID実装を使用するようになります。

クロスプラットフォームアプリケーションをAndroidで実行する

クロスプラットフォームアプリケーションをAndroidで実行し、以前と同様に動作することを確認します。

Androidログインアプリケーション

クロスプラットフォームアプリケーションをiOSで動作させる

Androidアプリケーションをクロスプラットフォーム化した後、iOSアプリケーションを作成し、その中で共有ビジネスロジックを再利用できます。

  1. XcodeでiOSプロジェクトを作成する
  2. KMPフレームワークを使用するようにiOSプロジェクトを設定する
  3. Android StudioでiOS実行構成を設定する
  4. iOSプロジェクトで共有モジュールを使用する

XcodeでiOSプロジェクトを作成する

  1. Xcodeで、File | New | Projectをクリックします。

  2. iOSアプリのテンプレートを選択し、Nextをクリックします。

    iOSプロジェクトテンプレート

  3. プロダクト名として「simpleLoginIOS」を指定し、Nextをクリックします。

    iOSプロジェクト設定

  4. プロジェクトの場所として、クロスプラットフォームアプリケーションが保存されているディレクトリ(例:kmp-integration-sample)を選択します。

Android Studioでは、以下の構造が得られます。

Android StudioでのiOSプロジェクト

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

Android StudioでリネームされたiOSプロジェクトディレクトリ

KMPフレームワークを使用するようにiOSプロジェクトを設定する

iOSアプリとKotlin Multiplatformによってビルドされたフレームワーク間の統合を直接設定できます。 この方法以外の代替手段については、iOS統合方法の概要で説明されていますが、このチュートリアルの範囲外です。

  1. Android Studioで、iosApp/simpleLoginIOS.xcodeprojディレクトリを右クリックし、 Open In | Open In Associated Applicationを選択して、XcodeでiOSプロジェクトを開きます。

  2. Xcodeで、Projectナビゲーターのプロジェクト名をダブルクリックして、iOSプロジェクト設定を開きます。

  3. 左側のTargetsセクションでsimpleLoginIOSを選択し、Build Phasesタブをクリックします。

  4. **+**アイコンをクリックし、New Run Script Phaseを選択します。

    Run Scriptフェーズを追加

  5. ランスクリプトフィールドに以下のスクリプトを貼り付けます。

    text
    cd "$SRCROOT/.."
    ./gradlew :shared:embedAndSignAppleFrameworkForXcode

    スクリプトを追加

  6. Based on dependency analysisオプションを無効にします。

    これにより、Xcodeがビルドごとにスクリプトを実行し、出力依存関係の欠落に関する警告が毎回表示されないようになります。

  7. Run ScriptフェーズをCompile Sourcesフェーズの前に移動させます。

    Run Scriptフェーズを移動

  8. Build Settingsタブで、Build Optionsの下にあるUser Script Sandboxingオプションを無効にします。

    ユーザー・スクリプト・サンドボックス化

    デフォルトのDebugまたはReleaseとは異なるカスタムビルド設定を使用している場合、Build SettingsタブのUser-Definedの下にKOTLIN_FRAMEWORK_BUILD_TYPE設定を追加し、DebugまたはReleaseに設定します。

  9. 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に戻ります。

  1. メインメニューでFile | Sync Project with Gradle Filesを選択します。Android Studioは自動的にsimpleLoginIOSという実行構成を生成します。

    Android Studioは自動的にsimpleLoginIOSという実行構成を生成し、iosAppディレクトリをリンクされたXcodeプロジェクトとしてマークします。

  2. 実行構成のリストでsimpleLoginIOSを選択します。 iOSエミュレーターを選択し、RunをクリックしてiOSアプリが正しく実行されることを確認します。

    実行構成のリストにあるiOS実行構成

iOSプロジェクトで共有モジュールを使用する

sharedモジュールのbuild.gradle.ktsファイルは、各iOSターゲットのbinaries.framework.baseNameプロパティをsharedKitとして定義しています。 これは、Kotlin MultiplatformがiOSアプリが利用するためにビルドするフレームワークの名前です。

統合をテストするために、Swiftコードで共通コードを呼び出します。

  1. Android Studioで、iosApp/simpleloginIOS/ContentView.swiftファイルを開き、フレームワークをインポートします。

    swift
    import sharedKit
  2. 正しく接続されていることを確認するには、ContentView構造をクロスプラットフォームアプリの共有モジュールからgreet()関数を使用するように変更します。

    swift
    struct ContentView: View {
        var body: some View {
            Text(Greeting().greet())
            .padding()
        }
    }
  3. Android StudioのiOS実行構成を使用してアプリを実行し、結果を確認します。

    共有モジュールからの挨拶

  4. ContentView.swiftファイルのコードを再度更新し、共有モジュールのビジネスロジックを使用してアプリケーションUIをレンダリングします。

    kotlin
  5. simpleLoginIOSApp.swiftファイルで、sharedKitモジュールをインポートし、ContentView()関数の引数を指定します。

    swift
    import SwiftUI
    import sharedKit
    
    @main
    struct SimpleLoginIOSApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView(viewModel: .init(loginRepository: LoginRepository(dataSource: LoginDataSource()), loginValidator: LoginDataValidator()))
            }
        }
    }
  6. iOS実行構成を再度実行し、iOSアプリがログインフォームを表示することを確認します。

  7. ユーザー名に「Jane」を、パスワードに「password」を入力します。

  8. 以前に統合を設定したため、iOSアプリは共通コードを使用して入力を検証します。

    シンプルなログインアプリケーション

結果を楽しむ – ロジックの更新は一度だけ

これでアプリケーションはクロスプラットフォームになりました。sharedモジュールのビジネスロジックを更新すると、AndroidとiOSの両方で結果を確認できます。

  1. ユーザーのパスワードの検証ロジックを変更します。「password」が有効なオプションであってはなりません。 そのためには、LoginDataValidatorクラスのcheckPassword()関数を更新します(すばやく見つけるには、を2回押し、クラス名を貼り付けてClassesタブに切り替えます)。

    kotlin
    package 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
            }
        }
    //...
    }
  2. Android StudioからiOSおよびAndroidアプリケーションの両方を実行し、変更を確認します。

    AndroidおよびiOSアプリケーションのパスワードエラー

このチュートリアルの最終コードを確認できます。

他に共有できるものは?

アプリケーションのビジネスロジックを共有しましたが、アプリケーションの他のレイヤーも共有することに決定できます。 たとえば、ViewModelクラスのコードはAndroidiOSアプリケーションでほとんど同じであり、モバイルアプリケーションが同じプレゼンテーション層を持つべきであれば、それを共有できます。

次のステップ

Androidアプリケーションをクロスプラットフォーム化した後、さらに次のことができます。

Compose Multiplatformを使用して、すべてのプラットフォームで統一されたUIを作成できます。

コミュニティリソースも確認できます。