【Android】Kotlin Multiplatformのサンプルプロジェクトを作ってみる

Kotlin MultiplatformではiOSやAndroidで共通の処理となるロジックやモデルなどをKotlinで書き、UIやプラットフォーム固有の部分についてはネイティブに任せます。Compose Multiplatformを利用すればプラットフォーム間で共通のUIを作成することも可能です。

KMPとCMPの関係性について

  • Kotlin Multiplatformはビジネスロジックや共通処理を異なるプラットフォームで共有
  • Compose MultiplatformはUIをマルチプラットフォームで共有

疑問点

CMP実装の試してみようかと思っていたが、書き方自体がほぼJetpack Composeと同じ書き方っぽい。KMPでマルチプラットフォーム対応しているなら、Jetpack Compose × KMPでダメなのか?

Jetpack ComposeはAndroid依存でandroidx.compose.*ライブラリを利用しているため、iOSでは利用することができない(iOS用のKotlin/Nativeコンパイラがandroidx.compose.*を解釈できない)

JetBrainsが提供しているCMP専用のorg.jetbrains.composeをGradleに追加して下記の依存関係を定義することでiOS向けなどでCMPライブラリが利用できるようになる

Kotlin
build.gradle.kts

sourceSets {
  commmonMain.dependencies {
    implementation(compose.runtime)
    implementation(compose.foundation)
    implementation(compose.material3)
  }
}

CMPにはマルチプラットフォームでビルドしたり、frameworkとして配布する仕組みはないためKMP基盤で成り立っている

KMPプロジェクトの作成方法について

プラグインなしで自力でGradleの周りの設定(ソースセット・ターゲット・依存関係)などを定義しようと試しましたが、エラーが出て難しかったので諦めました。

プラグインを利用する方法

Android Studioで「Kotlin Multiplatform」のプラグインをインストールする

    New Project → Generic → Kotlin Multiplatformを選択

      プロジェクトの設定後、プラットフォームを選択する画面が出るのでiOSのShared UIが選択されている状態で進めます

          そうするとCMPを利用する際に必要な設定が全て完了された状態でプロジェクトが作成されます

          Gradle設定周りで確認するファイルはCMPモジュールのbuild.gradle.kts

          iOS用のFrameworkを生成する

          下記で利用可能なGradleタスクを確認することができます。

          Kotlin
          ./gradlew tasks

          cocoapodsを設定ファイルで指定することでpodPublishDebugXCFrameworkタスクを実行してframeworkを生成することもできるみたいですが理解できていません…

          プラグインを利用したサンプルプロジェクトの場合

          cocoapodsを利用していないのでassembleDebugXCFrameworkなどになると思っていたのですが見つからず、linkDebugFrameworkIosArm64やlinkDebugFrameworkIosSimulatorArm64などを実行するとフレームワークが生成されました。

          Kotlin
          KMPSample/composeApp/build/bin/debugFramework/ComposeApp.framework

          サンプルプロジェクトではXcodeのBuild Phaseに下記が追加されていました。

          Kotlin
          cd "$SRCROOT/.."
          ./gradlew :composeApp:embedAndSignAppleFrameworkForXcode

          ※プロジェクトにComposeApp.frameworkの追加やBuild Settings → Framework Search Pathsの設定が不要(誤情報でしたら申し訳ないです)

          iOSでCMPコードを呼び出す

          プラグインを利用した場合はiosAppという名前でiOSプロジェクトも一緒に作成されており、SwiftUIでCMPのコードを呼び出す実装まで一緒に作成されていました。

          UIViewControllerRepresentableを利用してSwiftUIで利用する

          UIKitで利用する

          commonMainとiosMainで定義について

          サンプルプロジェクトではSwift側で利用できるようにするためにiosMainの中で ComposeUIViewControllerを生成

          プラットフォーム共通のものはcommonMainで定義して利用することができるが、AndroidやiOS固有のAPIを呼び出したい時はexpect/actualで定義する必要がある

          Kotlin
          [commonMain]
          
          @Composable
          expect fun hoge() 
          Kotlin
          [iosMain]
          import platform.UIKit.UIApplication
          
          @Composable
          actual fun hoge() {
             UIApplication.sharedApplication.canOpenUrl() {
             
             }
             // Composableを返す
           }

          プロジェクトのビルドについて

          ビルドする時はiosAppを選択してシミュレータで確認が可能

          ビルドするとUIがマテリアル…

          UIKit側のUIButtonなどに置き換えられているのではなく、Composableがそのまま描画されているみたいですね

          おわりに

          CMPではJetpack Composeの書き方でUI構築するのでAndroidが用意しているプラクティスなどを元に練習していこうと思います。