はじめに!
今年久しぶりに ACCESS Advent Calendar 2022 参加させていただきます!
こちらはACCESS Advent calendarの24日目の記事となります。
今回の記事のテーマは Kotlin のマルチプラットフォームモバイル開発 を試してみた記事です。
書かれていたコードは全部サンプルプロジェクト作ったら生成されているので、興味がありましたら、是非参考のリンクを辿って、試してみてください。
Kotlin Multiplatform Mobile (KMM) とは?
Kotlin Multiplatform Mobile (KMM) は Android と iOS を開発するための Kotlin の SDK です。
以下の図を見ると、ビジネスロジックは共通することができますが、View は各 OS の実装になります。
Shared Code は Kotlin で書きます。
Kotlin Multiplatform の survey によると、共通できるコードは以下のようです。
ロジックのところは共通できるが、UI のところを共通するのは難しいところがあると思われます。
Netflix や VMWare などがこの技術を利用しているらしいです。
現在 KMM はまだβ版ですが、近いうちに stable になるのを期待しています!
メリット・デメリット
メリットに関して、他の multiplatform と同じように、同じコードの内容を各OSのために書く量が少なくなります。
特別な API を使うとかは話は別ですが、これを使うことで、同じロジックの場合、1回だけ書くことで済みます。
一番感じられているのは元々 Android エンジニアの場合、楽に学習することができると思います。
iOS は Swift を使う場合、短時間でなれることが可能かもしれません。
デメリットに関しては、この SDK はまだβ段階ですので、正式にリリースされた時に、API が変わったり、機能が使えなくなったりすることがあるので、migration が大変になる可能性はなくはないと思います。
またいろんなバグもまだありますので、気をつけてしながら使いましょう。
Flutter という multiplatform SDK もあります。
KMM のアッポロッチは Flutter と少し違います。
- Flutter はできるだけ native のコードから abstract 化で実装されていますが、KMM はその逆で、native コードと共に動いているのは目標のようです。
- Flutter で UI を作るために Flutter の widget とかを使うことができますが、KMM では上書いたようにビジネスロジックは共通させることができますが、UI のところは、各 OS の UI を用いて実装します。例えば Jetpack Compose とか、SwiftUI とかを使います。
- デメリットは同じ UI なのに、各 OS のために書く必要があります。
- しかし、これはメリットとしても考えられます。例えば、直接 native component を使っているので、変な動きになってしまう心配はありません。また新しい UI component をすぐ使うことができるとかです。
ですので、native アプリに近いものを実現したい場合は KMM の方が良いかなと思っています。
環境設定
注意:iOS の特別のコードを書いたり、Simulator や実機で実行するため、Mac は必要です。
macOS の場合、kdoctor を実行して、環境がすでに整ったか確認できます。
brew install kdoctor
で kdoctor をインストールすることができます。
実行すると、環境によって、以下のような結果を得られます。
Your system is ready
と言われても、Android Studio の KMM plugin がまだインストールされていないのが検知できなかったので、もう一度環境を確認しましょう。
Kotlin の公式サイトによると、以下はインストールする必要があります。
- Android Studio
- XCode
- JDK
- Kotlin Multiplatform Mobile Plugin
- Kotlin Plugin
細かいところは触れませんが、基本的に最新の stable のツールを使ってもいいです。
最新で問題が発生した場合、少し古めのバージョンを使ってください。
私が使っているバージョンは以下になります。
Android Studio Chipmunk | 2021.2.1 Patch 1
Build #AI-212.5712.43.2112.8609683, built on May 19, 2022
Runtime version: 11.0.12+0-b1504.28-7817840 x86_64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
macOS 13.0.1
XCode Version 14.1 (14B47b)
java 18.0.1.1 2022-04-22
Java(TM) SE Runtime Environment (build 18.0.1.1+2-6)
Java HotSpot(TM) 64-Bit Server VM (build 18.0.1.1+2-6, mixed mode, sharing)
Kotlin 212-1.7.10-release-333-AS5457.46
Kotlin Multiplatform Mobile 0.3.4(212-1.7.11-357-IJ)-116
プロジェクト作り
KMM プロジェクトを作成してみました。
[File] → [New] → [New Project...] から、Kotlin Multiplatform App を選択します。
Android API を選択する時に気になりますが、API 21 (Android 5.0) の選択もありますので、一番古いのを試してみます。
Finish 押したら、Hello world プロジェクトは完成です!
プロジェクト structure
KMM では Root Project という表現があります。Root Project は共有モジュールを持ち、Android と iOS アプリはサブプロジェクトになります。
このリンクができたのは Gradle multi-project mechanism を使っているからです。
Root Project と別に、Android プロジェクトは自分のプロジェクトがあり、iOS も同様です。
デフォルトプロジェクトのファイルツリーは以下のような感じです。
各 OS の特別のコードの場合は HelloKMMAndroid と HelloKMMiOS で書くことになります。
両方の場合は Shared のところに書きます。これで実行して見ます。
Dependency
Android 開発をやると、build.gradle
に関わることがあると思います。
KMM でも似たようなものがありまして、Shared
ディレクトリで build.gradle.kts
というファイルがあります。
このファイルで共通の depenndency、android 用の dependency、iOS 用の dependecy を指定することができます。
プロジェクトを作成すると、デフォルトの build.gradle.kts
はすでに準備されています。
KMM では各 Source set の dependency を指定することができます。
Source Set のお主な項目は
- common main
- android main
- iOS main
main は test というペアがあります。
実際にサンプルプロジェクトからデフォルトのものはあります。定義は以下のような定義になっています。
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
}
kotlin {
android()
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
version = "1.0"
ios.deploymentTarget = "14.1"
podfile = project.file("../HelloKMMiOS/Podfile")
framework {
baseName = "shared"
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
namespace = "com.rh.hellokmm"
compileSdk = 32
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
SDK Dependencies に関して、対応しているか、していないかはありますので、以下にご参考頂ければと思います。
https://kotlinlang.org/docs/multiplatform-add-dependencies.html
https://kotlinlang.org/docs/multiplatform-mobile-android-dependencies.html
https://kotlinlang.org/docs/multiplatform-mobile-ios-dependencies.html
コードの流れ
サンプルプロジェクトからの内容ですが、main ロジックは以下になります。
Shared → src → commonMain → kotlin → com.rh.hellokmm → Greeting.kt のところで greeting 関数を書いてあります。
package com.rh.hellokmm
class Greeting {
private val platform: Platform = getPlatform() // こちらは各 [os]Main で実装されています。
fun greeting(): String {
return "Hello, ${platform.name}!"
}
}
getPlatform は commonMain のところでは interface だけを準備し、関数の中身は各 [os]Main で実装されています。
commonMain は以下です。
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
Android は
class AndroidPlatform : Platform {
override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}
actual fun getPlatform(): Platform = AndroidPlatform()
iOS は
import platform.UIKit.UIDevice
class IOSPlatform: Platform {
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
actual fun getPlatform(): Platform = IOSPlatform()
各OS のビューを実装しなければならないので、Android の MainActivity は以下のような実装です。(大事なところだけ書きました)
package com.rh.hellokmm.android
...
import com.rh.hellokmm.Greeting
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Greeting(Greeting().greeting())
}
}
}
}
}
@Composable
fun Greeting(text: String) {
Text(text = text)
}
...
iOS のビューは以下のような感じです。
import SwiftUI
import shared // こちらは大事です
struct ContentView: View {
let greet = Greeting().greeting()
var body: some View {
Text(greet)
}
}
...
実行
Android はそのまま Android Studio で実行したらできるんですが、iOS の方はちょっと一手間かかります。
XCode がまだ開いたことがない場合はまず、xcode を先に開く必要があります。その後 Android Studio に戻って、iOS のアプリを実行します。
実行されたら、Simulator が実行され、アプリが起動されます。1回実行したら、xcode が閉じたままでも問題ないです。
iOS の方を実行すると、アプリをビルドし、simulator で実行させるため、裏で以下のコマンドのような実行されています。
Command line invocation:
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -workspace /Users/KotlinMultiplatform/HelloKMM/HelloKMMiOS/HelloKMMiOS.xcworkspace -scheme HelloKMMiOS -configuration Debug OBJROOT=/Users/KotlinMultiplatform/HelloKMM/build/ios SYMROOT=/Users/KotlinMultiplatform/HelloKMM/build/ios -sdk iphonesimulator -arch x86_64
感想
- 現在はデフォルトのプロジェクトを実行してみるまでですが、わからないところもまだ多く、もっと色々試したかったです。
- Android エンジニアの場合、iOS を対応したかったら、IDE も慣れているし、kotlin を既に使えば、KMM を使うと学習曲線が急勾配ではないと感じています。
- 他の multiplatform の SDK を比較することがまだできていないです。
- デバッグは気になります。Android は Android Studio でそのままデバッグすることができますが、iOS の方は一手間かかないと出来なさそうです。View Hierarchy を確認したい時とか。(調査必要)
参考
この記事は以下に参考で書きました。
https://kotlinlang.org/lp/mobile/
https://blog.jetbrains.com/kotlin/2022/10/kmm-beta/
https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html
https://developers.cyberagent.co.jp/blog/archives/37611/
https://kotlinlang.org/docs/multiplatform.html#code-sharing-between-platforms
https://davidserrano.io/kotlin-multiplatform-mobile-goes-beta-what-is-kmm-how-does-it-compare-with-flutter-first-steps-to-create-a-kmm-app