Compose Multiplatform で始めるクロスプラットフォーム開発

2024.06.09

Kotlin Multiplatform App は、これまでiOSアプリのUIはSwiftUIで書く必要があった。私個人としては、UIをSwiftUIで実装するのであれば、Swiftネイティブで実装した方が早いと考えているため、結局KMPに興味はあれどチュートリアルを触るところで終わっていた。ここ半年ほどは仕事以外のプログラミングをする暇と余裕がなく、趣味のアプリ開発から離れていた。つい先日、Compose Multiplatform(以下CMP)が実用段階に入ったと聞き、CMPを使ってみようと試すことにした。

まずは、Compose Multiplatform AppがAndroidエミュレータおよびiOSシミュレータで動くようにセットアップした。本記事では、ウィザードから作成したスケルトンプロジェクトを実行するまでの手順を紹介する。

開発環境

  • MacBook Pro (16インチ, 2021)
  • macOS Sonoma 14.5
  • Xcode 15.4
  • Android Studio Jellyfish | 2023.3.1

セットアップ

XcodeとAndroid Studioがインストールされており、iOSアプリとAndroidアプリの開発をしているMacBook Proを使っている。Kotlinなどはすでにインストール済みのため、ここでは割愛する。

kdoctor を使って正しく環境設定ができているか確認しよう。KDoctorは、Kotlin Multiplatform (KMP) プロジェクトの開発環境が正しく設定されているかをチェックするツールだ。macOSを使っている場合、Homebrewからインストールできる。

brew install kdoctor

インストール後、以下のコマンドを実行してシステムの設定を確認する。

kdoctor

出力されるレポートには、KMP開発環境に関する情報が含まれている。すべてのチェックをパスしていることを確認しよう。

% kdoctor
Environment diagnose (to see all details, use -v option):
[✓] Operation System
[✓] Java
[✓] Android Studio
[✓] Xcode
[✓] CocoaPods

Conclusion:
  ✓ Your operation system is ready for Kotlin Multiplatform Mobile Development!

プロジェクトの新規作成

Kotlin Multiplatform wizard」を使って、アプリのスケルトンプロジェクトを作成しよう。

手順は以下の通り。

  1. プロジェクト名とパッケージ名を入力する。
  2. プラットフォームとして、AndroidとiOSを選択する。
  3. UI Implementationで「Share UI」を選ぶ。
  4. Downloadボタンをクリックする
  5. ダウンロードしたプロジェクトをAndroid Stuidoで開く

アプリをAndroidエミュレータで起動する

  1. 右上のcomposeAppを選択し、「Run」ボタンをクリックする
  2. エミュレータが起動し、アプリがデプロイされるまで待つ

アプリをiOSシミュレータで起動する

  1. 右上のiosAppを選択し、「Run」ボタンをクリックする
  2. シミュレータが起動し、アプリがデプロイされるまで待つ

以上で、Compose Multiplatformを使用してAndroidとiOSで同じUIを共有するアプリを作成し、AndroidエミュレータおよびiOSシミュレータで実行できるようになった

UIのレンダリングについて

スケルトンプロジェクトは、Click me!ボタンが押されたら画像を表示するという簡単なものだ。どのように実装されているのか確認していこう。App.kt を開くと前述の実装が確認できる。本当にComposeで書いたUIがiOSでも動いているようだ。

@Composable
@Preview
fun App() {
    MaterialTheme {
        var showContent by remember { mutableStateOf(false) }
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            Button(onClick = { showContent = !showContent }) {
                Text("Click me!")
            }
            AnimatedVisibility(showContent) {
                val greeting = remember { Greeting().greet() }
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(painterResource(Res.drawable.compose_multiplatform), null)
                    Text("Compose: $greeting")
                }
            }
        }
    }
}

Compose Multiplatformのalpha版がリリースされた時から変わっていなければ、iOS の場合には、CMPのユーザーインターフェースは Skiko というグラフィックスライブラリを使ってレンダリングしている。つまり、Compose の Button が SwiftUI の Button として実行されるわけではない。このあたりの考え方は Flutter と同じだと思う。

次に、XcodeでiosApp側のプロジェクトを開いてみよう。MainViewControllerKt を UIViewControllerRepresentable でラップしていることがわかる。

import UIKit
import SwiftUI
import ComposeApp

struct ComposeView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        MainViewControllerKt.MainViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        ComposeView()
                .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
    }
}

MainViewControllerKt の実装は、MainViewController.kt にて実装されている。こちらもシンプルな作りだ。SwiftUI を UIViewController として扱うには UIHostingController を使うが、それの Compose 版が ComposeUIViewControllerと考えるとわかりやすい。

import androidx.compose.ui.window.ComposeUIViewController

fun MainViewController() = ComposeUIViewController { App() }

まとめ

過去にKMPでビジネスロジックを集約したライブラリを作成して、iOSアプリで利用すれば開発効率がよいのではないかと考えていた時期がある。UseCaseやRepositoryをAndroid Studioで実装して、xcfrmameworkとして出力し、UIはXcodeでSwiftUIを使い実装するといった感じである。現実としては逆に煩雑になってしまい開発効率が下がってしまったため本採用を見送った。

Compose Multiplatformでは、KotlinとComposeを使用してAndroidとiOSで同じUIを共有するアプリを開発できるようになった。プラットフォーム間でのコードの再利用性が高まり、開発効率が大幅に向上するだろう。既にAndroidアプリをComposeで実装している場合、CMPはクロスプラットフォーム開発の第一候補に決まりだ。

今回紹介したのは簡単な実装だったため、iOSとAndroidの間で差異は感じられなかったが、実務として使う場合には細かい点で様々な課題も出てくると思う。現時点ではまだBeta版なので、さらなるアップデートや新機能の追加によって、開発者にとってますます便利なツールとなることを期待している。

参考記事