【KMP入門】プロジェクトの作成からサンプルアプリの起動まで
Kotlin Multiplatform (KMP) とは
AndroidネイティブアプリとiOSネイティブアプリを同時に開発するためのフレームワークです[1]。
本来はAndroidネイティブ用とiOSネイティブ用で別々のプロジェクトを作り、別々のコードを書き…とするところを、同じプロジェクトとしてまとめられ、一部のコードをKotlinで共通化できるようになります。
プロジェクトの作成
KMPで新規プロジェクトを作るには Kotlin Multiplatform Wizard を使用します。
今回はiOSの「Do not share UI (use only SwiftUI)」を選択します。他の設定は初期状態のままで構いません。
「Share UI (with Compose Multiplatform UI framework)」の方はCMPと呼ばれるもので、簡潔に言うとKotlin版Flutterなのですが、こちらはまた別の記事でご説明します。
Fleetのダウンロード
次に、KMP開発元の公式エディターであるFleetをダウンロードします。
ダウンロードが完了したらFleetを起動し、「Open...」から先ほど作成したプロジェクトを開きます。
KMPプロジェクトの見方
ぐわーっとファイルとディレクトリーが並んでいて驚くと思いますが、重要なのは
- composeApp … Androidアプリ用のコード(Kotlin) + 設定ファイル
- iosApp … iOSアプリ用のコード(Swift) + 設定ファイル
- shared … Android/iOSで共有するコード(Kotlin) + 設定ファイル
の三つです。それ以外のファイルやディレクトリーは基本的に触らなくても開発できます。
とりあえずアプリを起動してみる
Kotlin Multiplatform Wizard で作成したプロジェクトには簡単なサンプルコードが含まれています。
まずはアプリを動かしてみた方が分かりやすいと思うので、シミュレーターで起動してみましょう。
実行は画面右上にあるRunボタンから出来ます。
Runボタンを押すと実行するデバイスの選択画面が出ます。
もし緑の雷マークが付いている場合はFleetが準備中なのでしばらく待ちましょう。
普段AndroidやiOSアプリを開発している方は最初から実行する機種名が出ていると思いますが、そうでない場合は「No devices available」という表示になるか、赤い🔴に!マークのアイコンが付きます。
また、iOSアプリとして実行できるのはmacのみです。WindowsやLinuxで開発している方はAndroidのみとなります。
実行に成功すると以下のようなアプリが立ち上がります。
「Click me!」と書かれたボタンをタップするとAndroid、iOSそれぞれに合わせたアイコンが表示されます。
コードを読んでみる
shared
shared
は前述した通り、Android、iOSで共有するコードが格納されています。
普段iOSアプリを開発している方はcommonMain/kotlin/org/example/project
などの深い階層に驚くと思いますが、Androidアプリ開発ではお馴染みの仕様です。
まずはcommonMain/kotlin/org/example/project/Greeting.kt
を読んでみます。
package org.example.project
class Greeting {
private val platform = getPlatform()
fun greet(): String {
return "Hello, ${platform.name}!"
}
}
Greeting
というクラスが定義されていて、greet()
メソッドではgetPlatform().name
で得られた文字列を返しているのが分かります。
getPlatform()
の定義はPlatform.kt
にあります。
package org.example.project
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
ところが、getPlatform()
はありますが実装がありません。
getPlatform()
の実装はandroidMain
のPlatform.android.kt
、iosMain
のPlatform.ios.kt
にそれぞれあります。
package org.example.project
import android.os.Build
class AndroidPlatform : Platform {
// OSのAPIレベルを取得
override val name: String = "Android ${Build.VERSION.SDK_INT}"
}
actual fun getPlatform(): Platform = AndroidPlatform()
package org.example.project
import platform.UIKit.UIDevice
class IOSPlatform : Platform {
// OSの種類とバージョンを取得
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}
actual fun getPlatform(): Platform = IOSPlatform()
Androidはもちろん、iOSもKotlinでネイティブ機能が呼び出せます。ただし画面に表示させたり、音を鳴らしたりする処理は共有コードとして書くことが出来ず、それぞれのネイティブコード(androidApp
、iosApp
内のコード)として書く必要があります。
このように、KMPの共有コードでは、一つのメソッドをそれぞれのプラットフォーム用に分けて実装することが出来ます。
もちろん、不要であれば全ての共有コードをcommonMain
内だけで完結することも可能です。
ちなみにshared/build.gradle.kts
では、共有コードで使用する外部ライブラリーを追加できます。
iosApp
普段iOSアプリを開発している人にはお馴染みのファイルが並んでいます。
import SwiftUI
import Shared
struct ContentView: View {
// SwiftUIで変数の更新を画面に反映させる、変数の宣言時に@Stateなどを付ける
@State private var showContent = false
var body: some View {
VStack {
Button("Click me!") {
withAnimation {
showContent = !showContent
}
}
if showContent {
VStack(spacing: 16) {
// Swiftのツバメのアイコンを表示
Image(systemName: "swift")
.font(.system(size: 200))
.foregroundColor(.accentColor)
// KMPの共通コードを呼び出して画面に表示させる
Text("SwiftUI: \(Greeting().greet())")
}
.transition(.move(edge: .top).combined(with: .opacity))
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
二行目のimport Shared
で、KMPの共有コードをインポートしています。
composeApp
src/androidMain
以下に、普段Androidアプリを開発している人にはお馴染みのファイルが並んでいます。
src/commonMain
には Jetpack Compose に依存しないファイルを入れるようです。
サンプルではcompose-multiplatform.xml
という名前で、アプリに表示させるComposeのアイコンデータ(↑の起動画面にある箱みたいなアイコン)が格納されています。
package org.example.project
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import kotlinproject.composeapp.generated.resources.Res
import kotlinproject.composeapp.generated.resources.compose_multiplatform
// @Composableを付けると変数の更新を画面に反映できるようになる
@Composable
@Preview
fun App() {
MaterialTheme {
// showContentが変化すると画面が更新される
var showContent by remember { mutableStateOf(false) }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { showContent = !showContent }) {
Text("Click me!")
}
AnimatedVisibility(showContent) {
// KMPの共通コードを呼び出して文字列を取得
val greeting = remember { Greeting().greet() }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
}
}
}
おわりに
最初にKMPでプロジェクトを作成すると複雑そうに見えますが、実際はAndroid、iOS、共有部分の三つに分かれているだけだとご理解いただけたら幸いです。
次回は簡単なカウンターアプリを作ってみます。
参考文献
備考
本記事は以下の環境で検証しました
Fleet: 1.44.151
Android Studio: 2024.1 (AI-241.18034.62.2411.12071903)
Xcode 16.2
-
更にはデスクトップアプリ、Web、サーバーサイドKotlinも一つのプロジェクトでまとめられ、一部のコードをKotlinで共通化できます ↩︎
Discussion