話題のkotlin Multiplatformを使ってみた(~実装まで)

# はじめに
基本、公式のチュートリアルをなぞってます。
https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html

公式チュートリアルは全文英語の記事となっているので、英語苦手だよーって方とかは参考にしていただけると幸いです。

いままでアプリ開発とかをしてこなかった方でも理解できるよう、深くまでは掘らず比較的ざっくりとした感じの記事構成となっていますので、あらかじめご了承ください。

# 前提
- kotlin multiplatformの環境構築が完了していること
https://qiita.com/atsu123456789/items/147e9e03d2018b20841b

# ロジックの実装
## 共通処理について
まずは`GreetingKMP/composeApp/src/commonMain/kotlin/com/jetbrains/greeting/Greeting.kt`の中身を書き換えてみます。

```Greeting.kt
package com.jetbrains.greeting

import kotlin.random.Random

class Greeting {
    private val platform: Platform = getPlatform()

    fun greet(): String {
        val firstWord = if (Random.nextBoolean()) "Hi!" else "Hello!"

        return "$firstWord Guess what this is! > ${platform.name.reversed()}!"
    }
}
```

ここでパスやファイル名はproject IDや project Nameによって決まっているものなので、公式チュートリアルどおり進めていれば同じになるはずです。

ここで一回、エミュレータで実行してみると以下のようになりました。

![スクリーンショット 2024-08-14 1.52.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/b64a7f95-e3bc-8950-5241-0b75e3e8950d.png)

androidとiOSで同じソースの内容が、反映されたことが分かりました。すごいぞ！

## 特定OSでの処理について
OSによって固有の処理を持たせたい場合は`androidMain`、`iosMain`の中に処理を記載します。
ただ、`commonMain`でinterfaceクラスを実装し、その実体クラスを使っての実装ができ、今回はプロジェクトの初期実装の処理の中身を追っていこうと思います。

まず`GreetingKMP/shared/src/commonMain/kotlin/com/jetbrains/greeting/Platform.kt`の中身を見てみます。中身は下記の通りです。

```Platform.kt
package com.jetbrains.greeting

interface Platform {
    val name: String
}

expect fun getPlatform(): Platform
```

ここではinterfaceクラスを作成していることが分かります。
ではこのinterfaceクラスを継承してどこで実体クラスを作成しているかというと、それがsharedディレクトリ内の`androidMain`と`iosMain`になります。

`GreetingKMP/shared/src/iosMain/kotlin/com/jetbrains/greeting/Platform.ios.kt`というファイルがあるので、その中を見てみます。

```Platform.ios.kt
package com.jetbrains.greeting

import platform.UIKit.UIDevice

class IOSPlatform: Platform {
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

actual fun getPlatform(): Platform = IOSPlatform()
```

class `IOSPlatform`はinterface `Platform`を継承していることが分かりますね。

同様に`GreetingKMP/shared/src/androidMain/kotlin/com/jetbrains/greeting/Platform.android.kt`も見てみましょう。

```Platform.android.kt
package com.jetbrains.greeting

import android.os.Build

class AndroidPlatform : Platform {
    override val name: String = "Android ${Build.VERSION.SDK_INT}"
}

actual fun getPlatform(): Platform = AndroidPlatform()
```

ここでもiOSと同様、class `AndroidPlatform`がinterface `Platform`を継承していることが分かります。

なので、両OSで同じ処理を実装したいときで、かつOSによって処理を分ける必要がある処理の場合、以下のような流れで実装することになるのかなと思いました。
<br>
1. `commonMain`配下?にinterfaceクラスを作成
1. そのinterfaceクラスを使って画面描画できるよう実装する(チュートリアルの例でいうと`App.kt`や`Greeting.kt`がそれに該当する)
1. 手順1で作成したinterfaceクラスを継承した実体クラスを`androidMain`、`iosMain`内でそれぞれ実装する


簡単な処理だけならば上記のような流れになるかなーと思いました。

# UIの実装
次にそれぞれのOSでUIの変更についての実装をしていきます
※ここではUIは共通のフレームを使用するのではなく、android、iOSで独自の実装をしていきます。
そのため、wizardでプロジェクトを作成する際にiOSの欄のラジオボタンで「Do not share UI (use only SwiftUI)」を選択しています。
https://kmp.jetbrains.com/?_gl=1*xnvfpr*_gcl_au*NDY4OTY0ODc2LjE3MjI5NDgwMjU.*_ga*ODA3MzE5NTEzLjE3MjI5NDgwMTI.*_ga_9J976DJZ68*MTcyMzU2NTI5Ny44LjEuMTcyMzU2ODMzMS41Ny4wLjA.

## Android
まずは`Greeting.kt`でgreetingメソッドが呼ばれた際にStringのリストを返すように実装します。

``` Greeting.kt
package com.jetbrains.greeting

import kotlin.random.Random

class Greeting {
    private val platform: Platform = getPlatform()

    fun greet(): List<String> = buildList {
        add(if (Random.nextBoolean()) "Hi!" else "Hello!")
        add("Guess what this is! > ${platform.name.reversed()}!")
    }
}
```

Androidの場合、このメソッドを呼び出している箇所は以下になります。
`GreetingKMP/composeApp/src/androidMain/kotlin/com/jetbrains/greeting/App.kt`

そのため、このファイルを修正していきます。

``` App.kt
package com.jetbrains.greeting

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.ui.tooling.preview.Preview

@Composable
@Preview
fun App() {
    MaterialTheme {
        val greeting = remember { Greeting().greet() }
        Column(
            modifier = Modifier.padding(all = 20.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            greeting.forEach { greeting ->
                Text(greeting)
                Divider()
            }
        }
    }
}
```

やっていることとしては、`Column`を使ってリストのようなUIにして、`Greeting().greeting()`で取得したStringの配列をforEachでそれぞれ取得し、`Text(greeting)`で文字列を表示しているだけのシンプルなコードになります。
※ライブラリなどは実装に合わせて適宜入れてください。

この状態でandroidエミュレータを実行すると以下のようになりました。

![Screenshot_1723568923.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/3111dc71-bfef-2dac-8830-a0bef99146e5.png)

`Greeting.kt`で生成したStringの配列がそのままリストとして生成されたことが分かります。

## iOS
つづいてiOSでも同様の実装を行なっていきます。
`Greeting.kt`はAndroidのところで修正したものをそのまま使用して、iOSもAndroidと同様にリストのUIで表示していきます。
iOSのUIのロジックについては以下で持っているので、そこを修正していきます。
`GreetingKMP/iosApp/iosApp/ContentView.swift`

``` COntentView.swift
import SwiftUI
import Shared

struct ContentView: View {
    let phrases = Greeting().greet()

    var body: some View {
        List(phrases, id: \.self) {
            Text($0)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

iOSもAndroidと同様でやっていることとしては、`Greeting().greeting()`でStringの配列を取得し、それをList()を使用して、Text表示しているだけです。

この状態でエミュレータ起動すると以下のようになりました。

![Simulator Screenshot - iPhone 15 - 2024-08-14 at 02.13.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/0befb9fb-3dc8-60d4-980d-4c219805fcb5.png)

iOSでもAndroid同様、`Greting().greeting()`で取得したStringの文字列をリストとして画面表示することができました。

# さいごに
一旦今回はここまでとさせていただきます

他のクロスプラットフォームを使用したことがないのでわからないのですが、iOS、androidでそれぞれのOSに適したUIを使用できることは良いなーと思いました。
swiftUIでiOSのUIを綺麗に描ける一方で、まだまだswiftUIではできないこともあるので、storybookとかは使用できないのかなーとか思ってたりですが、swiftUI、KMPでこれから色々できるようになっていくことを考えると楽しみですね！

次の記事でサードパーティのライブラリの追加等について書ければと思いますー

ここまで読んでくださりありがとうございました！


# 参考文献
https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-getting-started.html