Kotlin Multiplatform Mobile がアルファ段階に移行したので、チュートリアルを試す

# 背景

最近、Kotlin Multiplatform Mobile(KMM)がアルファ段階に移行したようです。

[Kotlin Multiplatform Mobile がアルファ段階に移行 – Kotlin Blog | JetBrains](https://blog.jetbrains.com/ja/kotlin/2020/09/kotlin-multiplatform-mobile-goes-alpha-ja/)

KMM とは、JetBrains が提供するクロスプラットフォーム対応のモバイル開発用 SDK です。
Kotlin のマルチプラットフォーム対応能力を駆使し、モバイルアプリケーションの構築体験を可能な限り楽しく効率的にするように設計されたさまざまなツールや機能を含んでいます。

見ていて面白そうだったので、試しにチュートリアルをやってみました。
※2020 年 10 月時点の情報なので、今後のアップデートにより変わる可能性があります。

# 開発環境

開発環境は以下の通りです。

- Android Studio 4.1 RC 3 以降
- Xcode 11.3 以降
- Kotlin 1.4.0 以降
- Java 1.8.0_73 以降
- macOS Catalina バージョン 10.15.6

# 環境構築

以下をベースに進めていきます。

[Getting started - Help | Kotlin Multiplatform Mobile Docs](https://kotlinlang.org/docs/mobile/getting-started.html)

## Android Studio version 4.1 RC 3 以降をインストール

[Android Studio Preview](https://developer.android.com/studio/preview)から、4.1 RC 3 か 4.2 CANARY 13 のいずれかをインストールします。
どちらでも大丈夫です。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/364326ad-6942-7902-1409-d46054581e9b.png)

## Kotlin のバージョンを 1.4.0 以降にアップデート

`Configure`->`Plugins`から、Kotlin を 1.4.0 以降にアップデートします。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/dc93ce5a-7dc1-f7b2-ead1-d83bdc59484b.png)

## Kotlin Multiplatform Mobile をインストール

`Configure`->`Plugins`から、Kotlin Multiplatform Mobile をインストールします。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/b4ec6974-ab37-8914-3f18-f6dd5eca8132.png)

インストール後、Android Studio を再起動します。

# はじめてのマルチプラットフォームアプリ作成

以下をベースに進めていきます。

[Create your first multiplatform application - Help | Kotlin Multiplatform Mobile Docs](https://kotlinlang.org/docs/mobile/create-first-app.html)

## プロジェクト作成

`Create New Project`から新規でプロジェクトを作成します。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/f0e78bdc-3049-a4cd-1f67-62307b66ad7e.png)

`Select a Project Template`より、`KMM Application`を選択し、`Next`をクリックします。

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/183e36b8-9bf2-06ac-1911-e6e4f719b10a.png)

各項目を以下の通り入力します。

| 項目          | 名称                       |
| ------------- | -------------------------- |
| Name          | KMM Application            |
| Package name  | com.example.kmmapplication |
| Save location | 任意のフォルダ             |

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/351193eb-9d47-5a6b-36b5-1aec4cf175da.png)

`Configure Activity`ではデフォルトのまま`Finish`をクリックします。

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/b2e3cdc2-f5e0-b94f-eb27-8be67e298894.png)

## ビルド

早速ビルドしてみます。
まずは Android から。

`androidApp`を選択してから、ビルド対象の端末を設定してビルドします。

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/d814fd3d-f93f-54da-1dcc-02077d6b7d6d.png)

すると、以下のように起動します。

![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/9a80220b-6183-d943-914c-66a314b5761d.png)

次に iOS を試してみます。
`iosApp`を選択し、`Edit Confiugrations...`をクリックします。

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/633999f8-2e83-c560-e2ca-247b0e245564.png)

`Execution target`で端末を設定し、ビルドします。

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/37179505-fecd-5448-f004-59ad0bca804e.png)

すると、以下のように起動します。

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/245b869e-bb63-263b-eb5c-0dc772bf6ae6.png)

## テストコード

各プラットフォームごとにテストコードが用意されており、それぞれ以下のようにファイルを開いて実行できます。

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/67de730a-8939-b7c9-2bcd-5c7272f85e48.png)

Android のほうは、`shared/src/androidTest/kotlin/com.example.kmmapplication.shared`の`androidTest.kt`にあります。

```kotlin
package com.example.kmmapplication.shared

import org.junit.Assert.assertTrue
import org.junit.Test

class AndroidGreetingTest {

    @Test
    fun testExample() {
        assertTrue("Check Android is mentioned", Greeting().greeting().contains("Android"))
    }
}
```

iOS のほうは、`shared/src/iosTest/kotlin/com.example.kmmapplication.shared`の`iosTest.kt`にあります。

```kotlin
package com.example.kmmapplication.shared

import kotlin.test.Test
import kotlin.test.assertTrue

class IosGreetingTest {

    @Test
    fun testExample() {
        assertTrue(Greeting().greeting().contains("iOS"), "Check iOS is mentioned")
    }
}
```

どちらも`Greeting().greeting()`が出力する文字列にプラットフォーム名が含まれているかのテストをしています。
`shared/src/commonTest/kotlin`という共通用のテストフォルダも用意されているようですが、デフォルトでは何も入っていませんでした。

## アプリケーションの更新

画面上では「Hello, [プラットフォーム名とバージョン]」という文字列が表示されていました。この文字列を生成しているクラスが`shared/src/commonMain/kotlin/com.example.kmmapplication.shared`の`Greeting.kt`にあります。

```kotlin
package com.example.kmmapplication.shared


class Greeting {
    fun greeting(): String {
        return "Hello, ${Platform().platform}!"
    }
}
```

このディレクトリには、Android と iOS の両方のプラットフォームの共有コードが保存されます。共有コードに変更を加えると、両方のアプリケーションに変更が表示されます。
試しに以下のように変更してみます。

```kotlin
class Greeting {
    fun greeting(): String {
        return "Guess what it is! > ${Platform().platform.reversed()}!"
    }
}
```

変更後、各プラットフォームでビルドしてみます。
いずれも変更が反映されていることを確認しました。

| Android | iOS  |
| ------- | ---- |
| ![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/f67e74b8-6c01-9cfe-cd8f-598d3b9f337a.png) | ![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/c6e4830c-9ab4-64dd-3001-60e250aa1994.png) |

最後に、Platform クラスについても確認してみます。
`commonMain/kotlin/com.example.kmmapplication.shared`の`Platform.kt`です。

```kotlin
package com.example.kmmapplication.shared

expect class Platform() {
    val platform: String
}
```

`expect`キーワードはマルチプラットフォーム専用のインタフェースにて使われるキーワードで、あるプラットフォームのクラスを使いたい場合に使うキーワードです。
`androidMain`と`iosMain`にもそれぞれ`Platform.kt`があります。

Android のほうは、`androidMain/kotlin/com.example.kmmapplication.shared`の`Platform.kt`です。

```kotlin
package com.example.kmmapplication.shared

actual class Platform actual constructor() {
    actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
```

iOS のほうは、`iosMain/kotlin/com.example.kmmapplication.shared`の`Platform.kt`です。

```kotlin
package com.example.kmmapplication.shared


import platform.UIKit.UIDevice

actual class Platform actual constructor() {
    actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
```

それぞれ`actual`キーワードが使われています。プラットフォームごとに、`expect`クラスを`actual`で実装する必要があります。
`interface`とそれを`implement`する`class`の関係に似ています。

# まとめ

Kotlin Multiplatform Mobile(KMM)がアルファ段階に移行したことを受けて、試しにチュートリアルをやってみました。
どうやら共通化するのはロジックのみで、UI は各プラットフォームごとに独自で実装する必要がありそうです。
さらに以下のようなハンズオンも用意されているので、時間があるときにまた試してみます。

[Hands-on: Networking and Data storage - Help | Kotlin Multiplatform Mobile Docs](https://kotlinlang.org/docs/mobile/hands-on-networking-data-storage.html)

# 参考 URL

- [Kotlin Multiplatform でアプリ作ってみた！ | メルカリエンジニアリング](https://engineering.mercari.com/blog/entry/2018-12-18-114010/)