モバイルチームの成長とKMM導入に向けて

# はじめに
株式会社RevCommでモバイルアプリを担当している木下です。
この記事は [2020年のRevCommアドベントカレンダー](https://qiita.com/advent-calendar/2020/revcomm) 19日目の記事になります。18日目は @zomaphone さんの [【既存の解析システムに対して pytest-mock と pydantic を活用してクイックに総合テストを実装した話】](https://qiita.com/zomaphone/items/b4effff3d568e43e8b7e) でした。

# MiiTel Phone Mobile アプリについて
開発している [MiiTel Phone Mobile](https://support.miitel.jp/hc/ja/articles/360030157492) アプリの主な機能はVoIPで、RevComm のメインプロダクトである [MiiTel](https://miitel.revcomm.co.jp/) のオプションとしてご利用いただけます。当初は [Cordova](https://cordova.apache.org/) で開発されたβ版アプリが無料で提供されていましたが、アプリを起動していないと着信が受けられないなどの不都合があり、これらのユーザー体験を改善するためにネイティブ(Swift/Kotlin)で開発し直したものが現行のアプリとなります。

# モバイルチームについて
モバイルアプリの開発を担うモバルチームの変遷を簡単に紹介します。

## 1. 一人チーム時代
約2年前にRevCommにジョインしてから、iOSとAndroidの両アプリを開発・リリースし、サポート対応や機能改善、OSアップデート対応などをほぼ一人でやってきました。アプリの実装だけでなく、モバイルアプリのための [通信サーバー](https://qiita.com/miy/items/bee724d1dc1dc3540838) の設定やサーバーアプリの改修なども対応してきました。

## 2. 新メンバー参入
今年の10月ついにモバイルチームに、新しいメンバーが2名加わりました。ともにiOSアプリとAndroidアプリの開発経験があり、どちらも安心して任せられるメンバーです。
私としては [フルリモート・フルフレックスタイム制を導入しているRevComm](https://qiita.com/hiratake55/items/c37585d93138781e940a) で社員を迎え入れるのが初めてでした。組織として急成長してきたことや、リモートワークを推奨していることにより、コミュニケーションの不足や取りづらさの課題を以前から感じていたため、この点を特に留意し、新しいメンバーが発言・活動しやすい環境にしてプロダクトの成長に貢献できるようなチーム作りを目指しました。具体的な内容については、また機会があれば紹介したいと思います。

## 3. そしてさらなる成長に向けて
新たなメンバーが加わってチームとして機能するようになり、開発のスピードも上がってきました。これまでのワンオペでは機能を追加するとしても、iOSアプリの実装とリリースをしてから、Androidも同様の対応をするというフローとなり、リリースのタイミングがズレることがほとんどでしたが、メンバーが増えたことで両アプリ同時に新機能をリリースするといったことも可能になってきました。
そして、これからさらにスピードを上げるために導入を検討しているのが、マルチプラットフォーム開発です。
一つの機能を追加するのに、SwiftとKotlinで同様のロジックを実装しなくてはいけない、というのは非効率であると感じており、これを解消するためにもマルチプラットフォーム開発を導入したいと考えています。
React Native、Flutter、KMM(Kotlin Multiplatform Mobile) などありますが、既存のコードを生かしつつ、徐々に共通化が進められそう、かつ言語的に従来のモバイルエンジニアでもスムーズに開発できそうな、KMM が有力であると考えています。私自身 [前職](https://devblog.thebase.in/entry/2018/12/17/110000) ではAndroidアプリをメインでやっていたこともあり、KotlinでiOSアプリも開発できたら嬉しいなと考えていたところで、 [Kotlin Multiplatform Mobile がアルファ段階に移行](https://blog.jetbrains.com/ja/kotlin/2020/09/kotlin-multiplatform-mobile-goes-alpha-ja/) というニュースを目にしたのをきっかけに、導入意欲が高まりました。

# KMM(Kotlin Multiplatform Mobile) 入門
以前から Kotlin/Native という形でマルチプラットフォーム向けの開発環境は提供されていましたが、KMM ではiOSとAndroidのモバイルアプリに特化し Android Studio などの IDEに統合可能な環境となっており、より簡単に扱えるようになったと認識しています。
個人的にFlutterは少し触っている（Widgetの充実具合や高速な Hot reload は素晴らしいですよね）のですが、KMM は未経験でしたのでこの機会(Advent Calendar)に少し触ってみようと思い、 `KMM Shared Module` を作成して、AndroidアプリとiOSアプリのそれぞれでインポートして動作させる、ことをやってみようと思います。


## 1. 開発環境
[公式サイト](https://kotlinlang.org/docs/mobile/setup.html) にも記載されていますが、次のような環境が必要となります。

- Android Studio 4.1 以降
- Xcode 11.3 以降
- (AS) Kotlin plugin 1.4.20 以降
- (AS) Kotlin Multiplatform Mobile plugin
- JDK

## 2. Androidプロジェクト作成
`KotlinNativeAndroid` というプロジェクトを作成しました。
![AndPrg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/a516df69-10be-6191-b20b-113f6d5f155c.png)

## 3. KMM Shared Module 作成
`KotlinNativeAndroid` を開いた状態でメニューの `File` > `New` > `New Module...` を選択し `KMM Shared Module` を選択してから `Next` をクリックします。
![kmm-module.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/a21c8497-f289-6f07-3579-a3fdef854e5d.png)

次の画面で `Generate packFoxXcode Gradle task` をチェックしてから `Finish` をクリックします。（他はデフォルトのままとしました）
![kmm-module2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/e90a7cbf-eacc-e0c1-2da2-5f3f726f97e2.png)

次のようなファイルが作成されました。
![kmm-module3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/e0f7b5bb-4372-11f4-4d28-79c32444261a.png)

以下のような内容となっており、OSのバージョンを含む文字列を返すメソッドがサンプルとして作成されていることが分かります。

```commonMain/.../Greeting.kt
package com.example.kmmsharedmodule

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

```commonMain/.../Platform.kt
package com.example.kmmsharedmodule

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

```androidMain/.../Platform.kt
package com.example.kmmsharedmodule

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

```iosMain/.../Platform.kt
package com.example.kmmsharedmodule

import platform.UIKit.UIDevice

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


`KotlinNativeAndroid` プロジェクト内のファイルも自動で変更されます。

```settings.gradle
include ':kmmsharedmodule' <- 追加
```

```gradle.properties
kotlin.mpp.enableGranularSourceSetsMetadata=true <- 追加
kotlin.native.enableDependencyPropagation=false <- 追加
```


## 4. Androidプロジェクト修正

```app/build.gradle
dependencies {
    implementation project(':kmmsharedmodule') <- 追加
}
```

`KMM Shared Module` から文字列を取得して、画面に表示する処理を実装します。

```MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityMainBinding.inflate(layoutInflater)
        binding.textView.text = Greeting().greeting()
        setContentView(binding.root)
    }
}
```

ここで実行してみると次のようなエラーが発生しました。

```
Manifest merger failed : uses-sdk:minSdkVersion 23 cannot be smaller than version 24 declared in library [:kmmsharedmodule] /Users/tkinoshita/kotlin-native-example/KotlinNativeAndroid/kmmsharedmodule/build/intermediates/library_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 23
	Suggestion: use a compatible library with a minSdk of at most 23,
		or increase this project's minSdk version to at least 24,
		or use tools:overrideLibrary="com.example.kmmsharedmodule" to force usage (may lead to runtime failures)
```

Moduleの `minSdkVersion` がアプリよりも高いことが原因のようですので、Module の `minSdkVersion` をデフォルト値の `24` から、アプリの設定と同じ `23` に変更します。また compileSdkVersion と targetSdkVersion もアプリに合わせました。

```build.gradle.kts
android {
    compileSdkVersion(30)
    defaultConfig {
        minSdkVersion(23)
        targetSdkVersion(30)
    }
```

## 5. Androidアプリ実行結果
正常に実行されると次のような表示となります。

|<img width=320 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/fca2dff8-f91e-4a80-6409-da04b9b101ca.png">|
|:-:|

## 6. Framework 出力
続いてiOSアプリでもModuleを利用していきますが、iOSアプリ(Xcode)で利用するためには、Framework 形式でライブラリを出力する必要があります。
`build.gradle.kts` の下の方に Framework を出力するためのタスクが記述されているので、これを利用します。
Terminal で次のコマンドを実行します。

```
./gradlew :kmmsharedmodule:build
```

成功すると、 `kmmsharedmodule/build/xcode-frameworks` 内に `kmmsharedmodule.framework` フォルダが作成され Framework が出力されます。


## 7. Xcodeプロジェクト作成
次に iOSアプリのプロジェクトを作成していきます。
Xcodeで `KotlinNativeiOS` という名前の `App` プロジェクトを新規作成します。
![iosPrg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/13366bbd-3dec-2b95-327b-8a307c6a2f1b.png)

## 8. Xcodeプロジェクト修正

`Build Settings` の `Framework Search Paths` に Framework のパスを追加します。ここでは `$(SRCROOT)/../KotlinNativeAndroid/kmmsharedmodule/build/xcode-frameworks` を設定しています。
![スクリーンショット 2020-12-18 6.26.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/41c32800-0b33-ce93-c288-08cf6636a906.png)


アプリに Framework を組み込むための設定を追加します。
![スクリーンショット 2020-12-18 6.30.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/ceb5930d-ad14-94c1-4ab1-4a906ebaf65c.png)

`KMM Shared Module` から文字列を取得して、画面に表示する処理を実装します。

```ViewController.swift
import UIKit
import kmmsharedmodule

class ViewController: UIViewController {

    @IBOutlet weak var textLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        textLabel.text = Greeting().greeting()
    }
}
```

## 9. iOSアプリ実行結果
正常に実行されると次のような表示となります。

|<img width=320 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12300/36da4eae-2e48-21dd-7d89-b23269f8c5d6.png">|
|:-:|

## 10. ソースコード一式
[こちらのリポジトリ](https://github.com/metal-president/kotlin-native-example/) で公開しています。

# おわりに

KMM Shared Moduleの作成から AndroidアプリとiOSアプリで利用する流れが把握でき、おおよその感触がつかめました。どこまでロジックが共通化できるかはまだ未知ですが、期待していたとおり簡単にモジュールの作成と利用ができましたので、これから積極的に採用していきたいと思います。

明日はモバイルバックエンド担当の @rhoboro さんです！！
実は @rhoboro さんもモバイルチームの一員としてバックエンドの開発を担当されています！


# 参考
- [Set up an environment for KMM development](https://kotlinlang.org/docs/mobile/setup.html)
- [Multi platform App development in Kotlin Native](https://atul-sharma-94062.medium.com/multi-platform-app-development-in-kotlin-native-836cc3af3a2)