はじめに
株式会社RevCommでモバイルアプリを担当している木下です。
この記事は 2020年のRevCommアドベントカレンダー 19日目の記事になります。18日目は @zomaphone さんの 【既存の解析システムに対して pytest-mock と pydantic を活用してクイックに総合テストを実装した話】 でした。
MiiTel Phone Mobile アプリについて
開発している MiiTel Phone Mobile アプリの主な機能はVoIPで、RevComm のメインプロダクトである MiiTel のオプションとしてご利用いただけます。当初は Cordova で開発されたβ版アプリが無料で提供されていましたが、アプリを起動していないと着信が受けられないなどの不都合があり、これらのユーザー体験を改善するためにネイティブ(Swift/Kotlin)で開発し直したものが現行のアプリとなります。
モバイルチームについて
モバイルアプリの開発を担うモバルチームの変遷を簡単に紹介します。
1. 一人チーム時代
約2年前にRevCommにジョインしてから、iOSとAndroidの両アプリを開発・リリースし、サポート対応や機能改善、OSアップデート対応などをほぼ一人でやってきました。アプリの実装だけでなく、モバイルアプリのための 通信サーバー の設定やサーバーアプリの改修なども対応してきました。
2. 新メンバー参入
今年の10月ついにモバイルチームに、新しいメンバーが2名加わりました。ともにiOSアプリとAndroidアプリの開発経験があり、どちらも安心して任せられるメンバーです。
私としては フルリモート・フルフレックスタイム制を導入しているRevComm で社員を迎え入れるのが初めてでした。組織として急成長してきたことや、リモートワークを推奨していることにより、コミュニケーションの不足や取りづらさの課題を以前から感じていたため、この点を特に留意し、新しいメンバーが発言・活動しやすい環境にしてプロダクトの成長に貢献できるようなチーム作りを目指しました。具体的な内容については、また機会があれば紹介したいと思います。
3. そしてさらなる成長に向けて
新たなメンバーが加わってチームとして機能するようになり、開発のスピードも上がってきました。これまでのワンオペでは機能を追加するとしても、iOSアプリの実装とリリースをしてから、Androidも同様の対応をするというフローとなり、リリースのタイミングがズレることがほとんどでしたが、メンバーが増えたことで両アプリ同時に新機能をリリースするといったことも可能になってきました。
そして、これからさらにスピードを上げるために導入を検討しているのが、マルチプラットフォーム開発です。
一つの機能を追加するのに、SwiftとKotlinで同様のロジックを実装しなくてはいけない、というのは非効率であると感じており、これを解消するためにもマルチプラットフォーム開発を導入したいと考えています。
React Native、Flutter、KMM(Kotlin Multiplatform Mobile) などありますが、既存のコードを生かしつつ、徐々に共通化が進められそう、かつ言語的に従来のモバイルエンジニアでもスムーズに開発できそうな、KMM が有力であると考えています。私自身 前職 ではAndroidアプリをメインでやっていたこともあり、KotlinでiOSアプリも開発できたら嬉しいなと考えていたところで、 Kotlin Multiplatform Mobile がアルファ段階に移行 というニュースを目にしたのをきっかけに、導入意欲が高まりました。
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. 開発環境
公式サイト にも記載されていますが、次のような環境が必要となります。
- Android Studio 4.1 以降
- Xcode 11.3 以降
- (AS) Kotlin plugin 1.4.20 以降
- (AS) Kotlin Multiplatform Mobile plugin
- JDK
2. Androidプロジェクト作成
KotlinNativeAndroid
というプロジェクトを作成しました。
3. KMM Shared Module 作成
KotlinNativeAndroid
を開いた状態でメニューの File
> New
> New Module...
を選択し KMM Shared Module
を選択してから Next
をクリックします。
次の画面で Generate packFoxXcode Gradle task
をチェックしてから Finish
をクリックします。(他はデフォルトのままとしました)
以下のような内容となっており、OSのバージョンを含む文字列を返すメソッドがサンプルとして作成されていることが分かります。
package com.example.kmmsharedmodule
class Greeting {
fun greeting(): String {
return "Hello, ${Platform().platform}!"
}
}
package com.example.kmmsharedmodule
expect class Platform() {
val platform: String
}
package com.example.kmmsharedmodule
actual class Platform actual constructor() {
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
package com.example.kmmsharedmodule
import platform.UIKit.UIDevice
actual class Platform actual constructor() {
actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
KotlinNativeAndroid
プロジェクト内のファイルも自動で変更されます。
include ':kmmsharedmodule' <- 追加
kotlin.mpp.enableGranularSourceSetsMetadata=true <- 追加
kotlin.native.enableDependencyPropagation=false <- 追加
4. Androidプロジェクト修正
dependencies {
implementation project(':kmmsharedmodule') <- 追加
}
KMM Shared Module
から文字列を取得して、画面に表示する処理を実装します。
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 もアプリに合わせました。
android {
compileSdkVersion(30)
defaultConfig {
minSdkVersion(23)
targetSdkVersion(30)
}
5. Androidアプリ実行結果
正常に実行されると次のような表示となります。
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
プロジェクトを新規作成します。
8. Xcodeプロジェクト修正
Build Settings
の Framework Search Paths
に Framework のパスを追加します。ここでは $(SRCROOT)/../KotlinNativeAndroid/kmmsharedmodule/build/xcode-frameworks
を設定しています。
アプリに Framework を組み込むための設定を追加します。
KMM Shared Module
から文字列を取得して、画面に表示する処理を実装します。
import UIKit
import kmmsharedmodule
class ViewController: UIViewController {
@IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
textLabel.text = Greeting().greeting()
}
}
9. iOSアプリ実行結果
正常に実行されると次のような表示となります。
10. ソースコード一式
こちらのリポジトリ で公開しています。
おわりに
KMM Shared Moduleの作成から AndroidアプリとiOSアプリで利用する流れが把握でき、おおよその感触がつかめました。どこまでロジックが共通化できるかはまだ未知ですが、期待していたとおり簡単にモジュールの作成と利用ができましたので、これから積極的に採用していきたいと思います。
明日はモバイルバックエンド担当の @rhoboro さんです!!
実は @rhoboro さんもモバイルチームの一員としてバックエンドの開発を担当されています!