既存のAndroidプロジェクトをKMP化する手順

本田技研工業でAndroidアプリ開発を担当している須藤です。

Kotlin Multiplatform（以下KMP）の新規プロジェクトを作る方法はよく見かけますが、既存のAndroidプロジェクトをKMP化する方法がわからなかったので調べてみました。

手順はJetBrainsの「[**Make your Android application work on iOS – tutorial**](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-integrate-in-existing-app.html)」を参考に、自分用の備忘的な意味も兼ねてかなり最低限に絞ってまとめています。

詳細は上記をみていただくのがよいと思いますが、あまり時間がない中でざっくりとした手順を知りたい方や、KMPに馴染みがなく導入のイメージを掴みたい方の手助けになれば幸いです！


## 目次
1. [Wizardを使ってKotlin Multiplatform Sharedモジュールを追加する](#1wizardを使ってkotlin-multiplatform-sharedモジュールを追加する)
2. [必要なGradle記述を適宜追加する](#2必要なgradle記述を適宜追加する)
    1. [プロジェクトレベルのbuild.gradleにplugin記述を追加する](#1プロジェクトレベルのbuildgradleにplugin記述を追加する)
    2. [appモジュールのbuild.gradleにsharedモジュールを読み込むための記述を追加する](#2appモジュールのbuildgradleにsharedモジュールを読み込むための記述を追加する)
3. [共有したいロジックをSharedモジュール内に移動する](#3共有したいロジックをsharedモジュール内に移動する)
4. [プロジェクトルートにiOSプロジェクトを追加する](#4プロジェクトルートにiosプロジェクトを追加する)
5. [SharedモジュールをiOSで使えるようにするためのビルド設定を追加する](#5sharedモジュールをiosで使えるようにするためのビルド設定を追加する)
6. [iOS側のコードにKMPモジュールの参照箇所を実装する](#6ios側のコードにkmpモジュールの参照箇所を実装する)


KMPの概要についてはこちらの記事もおすすめです

https://qiita.com/seiji-matsumura/items/febd0c787e737e6345be


## 1.Wizardを使ってKotlin Multiplatform Sharedモジュールを追加する

まずは、既存のAndroidプロジェクト内に、共有ロジックを配置するためのSharedモジュールを追加していきます。

このSharedモジュールは、AndroidStudioのAdvanced Settingsから「Enable experimental Multiplatform IDE」の設定をONにすることでWizardを使って簡単に追加できるようになります。

下図のようにCreate New Module時に「Kotlin Multiplatform Shared Module」という項目を選択できるようになります。


![Create New Module](https://resources.jetbrains.com/help/img/kotlin-multiplatform-dev/stable/multiplatform-mobile-module-wizard.png)

引用：
https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-integrate-in-existing-app.html#make-your-code-cross-platform

モジュールを追加すると、

- Sharedモジュール用のbuild.gradle
- SharedモジュールのSourceSetで使用するディレクトリ構成（androidMain/commonMain/iosMain）
- 親プロジェクトからモジュールを参照できるようにするためのsetting.gradleのinclude記述
- 簡単なサンプルコード

などを追加してくれます。

## 2.必要なGradle記述を適宜追加する

上記のWizardで完全に設定を整えてくれるわけではなく、幾つかの追加記述が必要でした。

現時点では以下の記述を行いましたが、変更される可能性も十分あると思うので、適宜リファレンスを参考にしていただけるとよいと思います。

### 1.プロジェクトレベルのbuild.gradleにplugin記述を追加する

- Kotlin Multiplatformを使えるようにするためのプラグイン（org.jetbrains.kotlin.multiplatform）
- モジュールをプロジェクトで使えるようにするためのAndroidライブラリプラグイン（com.android.library）

をプロジェクトに適用します。

前者はKotlinバージョン、後者はAGPのバージョンと合わせます。

Kotlinバージョンは1.9.20-2.0.20である必要があります。

https://kotlinlang.org/docs/multiplatform-compatibility-guide.html#version-compatibility


サンプルコードは以下です。

```kotlin:project/build.grade.kts
plugins {
    alias(libs.plugins.kotlinMultiplatform) apply false
    alias(libs.plugins.androidLibrary) apply false
    ...
}
```


```toml:libs.versions.toml
[versions]
agp = "8.2.2"
kotlin = "2.0.0"
...

[plugins]
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
...
```

### 2.appモジュールのbuild.gradleにsharedモジュールを読み込むための記述を追加する

通常のモジュール同様、親となるappモジュールのbuild.gradleにdependenciesの定義を追加します。


```kotlin:app/build.gradle.kts
dependencies {
    implementation (project(":shared"))
    ...
}
```

## 3.共有したいロジックをSharedモジュール内に移動する

KMPで共有ロジックを実装するshared/commonMainソースセットではJavaに依存するコードが使えません。

そのため、恐らくほとんどのケースではAndroidプロジェクト内の共有したいロジックをそのままcommonMainに移しただけではエラーになってしまうと思います。

これらの処理をKMPでも使える記述に修正していく必要があります。

本家のチュートリアルは簡単なログイン画面を移行するシナリオになっているのですが、ここでは以下のような修正例が紹介されていました。

- ログイン時のエラーハンドリングにIOExceptionを使用していたが、Kotlin JVM環境だと使えないのでRuntimeExceptionに修正する
- メールのバリデーションにandroid.utilsのmatcher関数を使っていたが、Kotlin JVM環境だと使えないのでKotlinのmatches関数に書き換える
（android.utilsと違ってメールアドレス用の正規表現は用意されていないので自分で定数定義する）
- ユーザIDの発行にjava.util.UUIDのrandomUUID関数を使っていたがiOSだと使えないのでPlatformごとの実装（actual/expected）を行う
    - androidMainではそのままjava.util.UUIDクラスを使用し、iosMainではiOS用のplatform.Foundation.NSUUIDを使用する。

正直ここだけ聞くと、エラーの具体性を落としたり、車輪の再発明的な自前実装部分を増やしたりで微妙に見えるかもしれないですが、最近はKMP対応のライブラリもたくさん出てきているので既存の実装をあまり大きく変えずに移行できるケースもあると思います。

Kotlin Multiplatformでの実装の詳細については以下のチュートリアルをご参照ください。

https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-create-first-app.html

## 4.プロジェクトルートにiOSプロジェクトを追加する

iOSプロジェクトがまだない場合は、XcodeでiOSプロジェクトを新規作成する際に、作成先ディレクトリにプロジェクトのルートを指定します。

## 5.SharedモジュールをiOSで使えるようにするためのビルド設定を追加する

Sharedモジュールの実装をiOSプロジェクトで使えるようにするための設定を行います。

iOSではXcodeのBuild Phasesというメニューでビルド時に実行するスクリプトやタイミングを指定できます。

ここで「Compile Sources」フェーズの前に「Run Script」フェーズを追加し、以下のスクリプトを実行するように設定します。

```bash
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode
```

プロジェクトのルートディレクトリに移動して、フレームワークをXcode用に組み込むGradleコマンドをたたいているようですね。

> embedAndSignAppleFrameworkForXcode - Embed and sign  framework as requested by Xcode's environment variables
> 

![](https://resources.jetbrains.com/help/img/kotlin-multiplatform-dev/stable/xcode-add-run-phase-2.png)

引用：
https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-integrate-in-existing-app.html#make-your-code-cross-platform

合わせて、スクリプトの実行を許可するために、Build Settingのメニューで「User Script Sandboxing」の項目を無効にします。

![](https://resources.jetbrains.com/help/img/kotlin-multiplatform-dev/stable/disable-sandboxing-in-xcode-project-settings.png)


引用：
https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-integrate-in-existing-app.html#make-your-code-cross-platform


:::note warn
上記の設定だとマルチモジュール構成のiOSプロジェクトのパッケージ内からはKMPコードにアクセスすることができません。

iOSプロジェクトをマルチモジュール構成にする場合や、既存のマルチモジュール構成のiOSプロジェクトを使用したい場合は以下の方法を参考にしてください。
:::

https://qiita.com/usu_shin/items/376337451c2f4d3f0f69


## 6.iOS側のコードにKMPモジュールの参照箇所を実装する

上記の設定後にビルドすることでiOSプロジェクト内でsharedモジュールをimportできるようになります。

これでiOSプロジェクトからKMPモジュールにある実装を参照することができるようになります。

## **終わりに**

ここまで読んでいただきありがとうございました。

少しでもどなたかの参考になれば幸いです。