Open4

Kotlin Multiplatform Mobile

Shun UematsuShun Uematsu

環境構築

  1. Abdriud Studio(ver4.1以上)をインストール
  2. iOSのコードも動かしたい場合は、Xcode11.3以上をインストール
  3. Kotlin Pluginを1.4.20以上にアップデートする
  4. Kotlin Multiplatform Mobile Pluginをインストールする
    Preferences => Pluginsを選び、MarketplaceでKotlin Multiplatform Mobileを探し、それをインストールする
  5. java -versionでJDKがインストールされていることを確認してください

KMMプロジェクトを作成する

Shun UematsuShun Uematsu

expectとactual

プラットフォーム固有のAPIにアクセスする必要がある場合に使用する修飾子。
共通モジュール側で想定される処理を書き(expect)、専用モジュール側では実際の処理を書く(actual)。

expectとactualの書き方

  • expectとactualキーワードをつけたclass/funは同名でなければならない。
  • exepctキーワードをつけたclass/funには具体的な実装を書くことができない。(interfaceみたいな感じ?)

共有モジュールにデフォルトのactualの実装を書き、あるプラットフォームでも固有のactualを書きたい場合は、typealiasキーワードで解決することができる。

Shun UematsuShun Uematsu

KMMプロジェクトの構成について

##KMMとは
Kotlin Multiplatform Mobile(以下KMM)の目的は、AndroidとiOSのロジックを共通化することです。
ここでは基本的なKMMプロジェクトの構成について説明していきます。

基本的にKMMは3つのComponentから成り立っています。

  • Shared module:AndroidとiOSの両方で使用する共通ロジックが含まれています。
  • Android application:AndroidアプリにビルドされるKotlinモジュールです。ビルドシステムとしてGradleが使われます。
  • iOS application:iOSアプリにビルドされるXcode Projectです。

Root Projectについて

一番親のプロジェクト(Root Project)はShared moduleとAndroid applicationをサブプロジェクトとして保持するGradle Projectです。
これらはGradleの機構を用いて相互にリンクされます。

iOS ApplicationはXcode projectから生成され、Root Projectで別のディレクトリで保持されます。Xcodeは独自のビルドシステムを使用しているので、iOS applicationはGradleを介してKMMプロジェクトの他の部分と接続されていません。
その代わりに外部のアーティファクトつまりFrameworkとして、Shared moduleを使用します。

Root Projectはソースコードを持ちません。グローバルな値(トークンなど)を保持したい場合は、Root Project直下のbuild.gradleやgradle.propertiesに定義することができます。

KMMでマルチモジュール構成にしたい場合、ASでモジュールを作成し、それをinclude宣言で組み込んでください。

Shared moduleについて

Shared moduleはクラス、関数など両プラットフォームで使用されるアプリケーションロジックが含まれています。
しかし、AndroidとiOSに同じロジックを実装するためには、時々両プラットフォームそれぞれに実装する必要があります。kotlinのexpectactualというキーワードを使います。

共通モジュールのソースコードは、以下3つのソースセットで構成されています。

  • commonMain:expectキーワードを含む両プラットフォームで動くコードを保持します。
  • androidMain: actualキーワードを含む、Android固有のコードを保持します。
  • iOSMain: actualキーワードを含む、iOS固有のコードを保持します。

それぞれのソースセットは独自の依存関係を持ちます。
Kotlin標準ライブラリは全てのソースセットに自動的に追加されるため、ビルドスクリプトで宣言する必要はありません。

kotlin {
    sourceSets {
        val commonMain by getting
        val androidMain by getting {
            dependencies {
                implementation("androidx.core:core-ktx:1.2.0")
            }
        }
        val iosMain by getting
        // ...
    }
}

また単体テストにおける依存関係も上記と同様に定義することができます。命名はMainをTestにしてください。

kotlin {
    sourceSets {
        // ...
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        val androidTest by getting
        val iosTest by getting
    }
}

Android Libraryについて

Shared moduleから作成されたAndroid Libraryの構成は、Android Projectsでは一般的です。

Android Libraryを作成するためには、KMMに加えてさらに2つのGradleプラグインを使用します。

plugins {
    id("com.android.library")
    id("kotlin-android-extensions")
}

Android libraryについての設定は、通常のAndroid Applicationと同様にShared module内のbuild.gradleのトップレベルのandroid{}に保持します。

android {
    compileSdkVersion(29)
    defaultConfig {
        minSdkVersion(24)
        targetSdkVersion(29)
        versionCode = 1
        versionName = "1.0"
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
}

iOS Framework

iOSアプリケーションで使用する場合、Shared moduleはFrameworkにコンパイルされます。Frameworkとは、画像などのリソースや動的共有ライブラリなどを一つのパッケージにまとめたディレクトリのことです。
aarと似たような概念のものだと思います。

frameworkはKotlin/Nativeコンパイラを用いて作成されます。frameworkの設定は、kotlin{}内のビルドスクリプトのブロックiOS{}に保持されます。

kotlin {
    // ...
    ios {
        binaries {
            framework {
                baseName = "shared"
            }
        }
    }
}

https://kotlinlang.org/docs/mpp-build-native-binaries.html

iOSアプリケーションのビルドもとであるXcodeプロジェクトにFrameworkを公開するGradleタスクがあります。
iOSアプリケーションプロジェクトの構成を利用して、ビルドモードを定義し、指定された場所に適切なフレームワークバージョンを提供します。

val packForXcode by tasks.creating(Sync::class) {
    group = "build"
    val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
    val framework = kotlin.targets.getByName<KotlinNativeTarget>("ios").binaries.getFramework(mode)
    inputs.property("mode", mode)
    dependsOn(framework.linkTask)
    val targetDir = File(buildDir, "xcode-frameworks")
    from({ framework.outputDirectory })
    into(targetDir)
}

Android Application

KMMプロジェクトの一部であるAndroid applicationはKotlinで書かれている一般的なAndroid applicationです。
基本的なKMMプロジェクトでは、3つのGradleプラグインを使用します。

plugins {
    id("com.android.application")
    kotlin("android")
    id("kotlin-android-extensions")
}

Shared moduleにアクセスするために、Android applicationの依存関係として以下を定義します。

dependencies {
    implementation(project(":shared"))
    //..
}

この依存関係に加えて、Kotlin標準ライブラリと一般的なライブラリとの依存関係を定義します。

dependencies {
    //..
    implementation("androidx.core:core-ktx:1.2.0")
    implementation("androidx.appcompat:appcompat:1.1.0")
    implementation("androidx.constraintlayout:constraintlayout:1.1.3")
}

Android applicationの設定を、build.gradleのトップレベルのandroid {} に書きます。

android {
    compileSdkVersion(29)
    defaultConfig {
        applicationId = "org.example.androidApp"
        minSdkVersion(24)
        targetSdkVersion(29)
        versionCode = 1
        versionName = "1.0"
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
}

android {
compileSdkVersion(29)
defaultConfig {
applicationId = "org.example.androidApp"
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}

Shun UematsuShun Uematsu

Android Libraryについて

Android Libraryは、構造的にAndroid application moduleと同じです。
ソースコードやリソースファイル、AndroidManifestなど、アプリのビルドに必要なすべてのものを含めることができます。
ただし、アプリケーションモジュールのようにデバイス上で実行されるapkにコンパイルする代わりに、Android application moduleの依存関係として使用できるAndroidアーカイブ(aar)ファイルにコンパイルされます。
jarファイルとは異なり、aarファイルファイルは次の機能を提供します。

  • 前述の通り、androidリソースとAndroidManifestを含めることができます。これにより、レイアウトやDrawableなどの共有リソースをバンドルできます。
  • 無料版と有料版など複数のapkバリエーションが存在するアプリにおいて、両方に同じコアコンポーネントが必要な場合。

いずれの場合も、再利用するファイルをライブラリモジュールに移動してから、各アプリモジュールの依存関係としてライブラリを追加するだけです。

https://developer.android.com/studio/projects/android-library