araki tech

for developers including me

KMM (Kotlin Multiplatform Mobile) チュートリアルを読んで触ってみる

KMM (Kotlin Multiplatform Mobile) チュートリアル

はじめに: KMM (Kotlin Multiplatform Mobile) とは

KMM (Kotlin Multiplatform Mobile) チュートリアル
https://kotlinlang.org/lp/mobile/

KMM: Kotlin Multiplatform MobileJetbrains社が開発を行っているプログラミング言語 Kotlin を使って、AndroidやiOS向け、つまりマルチプラットフォーム向けにアプリを開発するために作られた SDK (Software Development Kit) です。

Kotlinは、現在ではJavaに変わるAndroidアプリ開発として広く利用されています。

筆者である私も、本業ではKotlinを使ったAndroid開発を行っており、Kotlinという言語の使い勝手の良さをよく知っています。

そのようなKotlinが最近では、先述したようにKMMと呼ばれるモバイルマルチプラットフォーム向けのSDKが人気になってきているようで、私自身も「この流れに乗らなければ!」という気持ちでこの記事を書くことに至りました。

なので、今回は公式の Get started with Kotlin Multiplatform Mobile (執筆時点で2023年4月8日が最終更新のものを参照) というドキュメントを読みながら一緒にKMMの概要を掴んでいきましょう。

開発環境のセットアップ

AndroidとiOSアプリの作成に入る前に、まずはKMMの環境構築を行います。

もちろんiOSアプリを動かすには、MacOS環境が必要です。

参考までに私の環境は、M1 Macbook Air (8GB RAM) です。

加えて下記の環境が必要となります。

ツール 補足
Android Studio 開発で利用するIDE (統合開発環境) です。

デバイスエミュレータもこのAndroid Studio上で管理、実行されます。

Xcode iOSアプリを作成する際に使うIDEですが、KMMでは主にバックグラウンドで動作します。

iOSアプリに対してSwiftやObject-Cのコードを利用せざるを得な苦なった場合に使います。

JDK (Java Development Kit) いわゆるJavaの開発環境です。

KotlinはJVM (Java Virtual Machine) 上で動作するJava互換言語なので、必要になります。

Kotlin Multiplatform Mobile plugin Android Studioのプラグインです。

設定方法は後述します。

Kotlin plugin 基本Android Studioに元から組み込まれています。

が、もしかしたらバージョンが古い可能性があるので、最新版の利用が推奨されます。




Kotlin Multiplatform Mobile plugin

Settings / Preferences > Pluginsよりインストール & 再起動してください。

プロジェクト選択画面からもPluginsに飛べます。

KMM (Kotlin Multiplatform Mobile) チュートリアル

KDocterでの環境チェック

MacOS環境ではKDockterと呼ばれるツールを使ってKMMの環境に不足がないかをチェックできます。

Homebrewでインストールして利用してみましょう。

$ brew install kdoctor
$ kdocter
Environment diagnose (to see all details, use -v option):
[✓] Operation System
[✓] Java
[✓] Android Studio
[✖] Xcode
  ✖ Xcode requires to perform the First Launch tasks
    Launch Xcode and complete setup
[✖] Cocoapods
  ✖ CocoaPods requires your terminal to be using UTF-8 encoding.
    Consider adding the following to ~/.zprofile
    export LC_ALL=en_US.UTF-8

Conclusion:
  ✖ KDoctor has diagnosed one or more problems while checking your environment.
    Please check the output for problem description and possible solutions.

このような感じで、不足している項目を教えてくれます。

上記Cocoapodsの指摘は具体的で良いのですが、Xcodeの「Launch Xcode and complete setup」は、ちょっと抽象的で戸惑いました。

調べてみると、このスレッド に辿り着き、私の場合これを試してみたところ解決しました。

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

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

プロジェクトを作る

早速プロジェクトを作ってみましょう。

プロジェクト名やアプリ名、min SDK ver、パッケージ名はなんでも良いのですが、”iOS framework distribution” については、まず最初は “Regular framework” の利用が推奨されているようです。

KMM (Kotlin Multiplatform Mobile) チュートリアル
Kotlin MultiPlatform Appを選択
KMM (Kotlin Multiplatform Mobile) チュートリアル
このあたりは適当な名前をつけてOK。Package nameは一意になることが望ましいので、通常は企業・個人ドメインなどを逆にして使うことが多い。
KMM (Kotlin Multiplatform Mobile) チュートリアル
公式ドキュメントに倣い、”Regular framework”を選択。

そうするとプロジェクトのベースとなるコードやファイルが多数生成されます。

“Project” を選択して生成されたファイルを見てみましょう。

KMM (Kotlin Multiplatform Mobile) チュートリアル

KMM (Kotlin Multiplatform Mobile) チュートリアル

このようにいくつかのディレクトリに分かれて管理されていることがわかります。

一番上の階層で見てみると、

  • Androidアプリ用ディレクトリ (今回の私の場合は “KMMSampleForAndriod)
  • iOSアプリ用ディレクトリ (今回の私の場合は “KMMSampleForiOS)
  • shared

という3つのディレクトリに分かれています。

見て想像がつくように、Androidアプリ固有のコード、iOSアプリ固有のコード、そしてsharedで共通のロジックを管理します。

さらに、sharedの中を覗いてみると、

  • androidMain
  • iosMain
  • commonMain

という3つのサブディレクトリがあります。

初期生成されたサンプルコードでは、プラットフォーム固有のバージョンを取得するロジックをinterfaceで切って、ビルド対象によってどの具象クラスを利用するかを判断しています

// commonMain/.../Platform.kt
package tech.araki.kmmsample

/**
 * プラットフォーム固有の情報を取得するためのインターフェース
 */
interface Platform {
    val name: String
}

/**
 * プラットフォームの情報を取得するための関数。
 * 
 * NOTE: expect修飾子はプラットフォーム固有の情報を得る時に用いる。
 *  実際に取得するロジックを含む関数には actual修飾子がつく。
 */
expect fun getPlatform(): Platform
// androidMain/.../Platform.kt
package tech.araki.kmmsample

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


actual fun getPlatform(): Platform = AndroidPlatform()
// iosMain/.../Platform.kt
package tech.araki.kmmsample

import platform.UIKit.UIDevice

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

actual fun getPlatform(): Platform = IOSPlatform()

ここで、見慣れないexpect / actualという関数についている修飾子が出てきます。

これらは、インターフェースと具象クラスの関係のようなもので、具象化するクラスや関数がプラットフォーム依存になる場合は、この修飾子をつけるようです。

ビルドをしてエミュレータで実行してみる

ともかくまずは動かしてみましょう。

まずはAndroidデバイスをターゲットにビルドして、インストールしてみます。

KMM (Kotlin Multiplatform Mobile) チュートリアル

今回は Pixel 5 (API 30) を使っていますが、なんでも良いです。

ビルドが成功しインストールされると、下記のように “Hello, Android 30!” という画面が出てきました。

KMM (Kotlin Multiplatform Mobile) チュートリアル

次にiOS版をビルドしてみましょう。

Build Configurationを iOS のものに変えます。

KMM (Kotlin Multiplatform Mobile) チュートリアル

そうしたらXcodeを立ち上げてから、実行▶︎を押します。

すると、よろしくiOSデバイスのエミュレータが立ち上がり、インストール&実行までしてくれます。

KMM (Kotlin Multiplatform Mobile) チュートリアル

エミュレータの種類やOSバージョンは、”Edit Configuration” から変更できます。

KMM (Kotlin Multiplatform Mobile) チュートリアル




少し手を加えてみる

せっかくなので、外部ライブラリを使って少しコードを改変してみましょう。

KMM含めKotlinのプロジェクトではGradleと呼ばれるビルドツールないし、プロジェクト管理ツールが用いられます。

共通ロジックにおいては、/shared/build.gradle.kts がそれにあたります。

公式に倣って、Kotlinx-datetimeと呼ばれるライブラリを取り込んで、使用してみましょう。
(Kotlinxにこんなライブラリがあったとは知りませんでした…。)

// build.gradle.kts
plugins {
    kotlin("multiplatform")
    id("com.android.library")
}

kotlin {
    android()
    
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "shared"
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")  // dependencyを追記
            }
        }
// ...

Sync nowでプロジェクトがリロードされ、Kotlinx-datetimeがプロジェクトで利用できるようになります。

KMM (Kotlin Multiplatform Mobile) チュートリアル

/shared/commonMain/kotlin/[package name]/以下に NewYear.kt を作成して、一つ新しいロジックを書きましょう。

package tech.araki.kmmsample

import kotlinx.datetime.*

/**
 * 年明けまでの残り日にちを返す関数
 */
fun daysUntilNewYear(): Int {
    val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
    val closestNewYear = LocalDate(today.year + 1, 1, 1)
    return today.daysUntil(closestNewYear)
}

早速下記修正をGreeting.ktに入れてみましょう。

そしてビルドしてエミュレータで動作させてみると、うまく修正が反映されていそうです。

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

    fun greet(): String {
        return "Hello, ${platform.name}!" +
                "\nThere are only ${daysUntilNewYear()} days left until New Year! 🎆"
    }
}

KMM (Kotlin Multiplatform Mobile) チュートリアル

このあとチュートリアルでは、AndroidとiOSそれぞれでViewを修正する流れになるのですが、長くなるのでこの記事ではここまでにします。

ちなみに、iOS用のViewディレクトリ (今回では KMMSamoleForiOS) をXcodeで開くと、プロジェクトとして認識され、そのまま修正に移ることができます。

なので、ViewについてはAndroidはAndroidStudioで、iOSはXcodeで開発を行う、といった形になるようです。

KMM (Kotlin Multiplatform Mobile) チュートリアル

おわりに

今回は、Kotlin Mulitiplatform Mobile (KMM) の触りだけ勉強がてらまとめてみました。

まとめてみた、と言っても公式のドキュメントをそのままやっているだけなので、そこまで価値のある記事ではありあせんが、これは個人的な勉強用メモですので、多めに見てください。

なんとなく概要は掴めたので、いつかKMMでマルチプラットフォームなアプリ開発をやってみたい気持ちはあります。

が、結局作りたいもののアイデアが重要なので、それがいつになるかは分かりません…。

余談

ちなみにこの記事のサムネの右下にいるキャラクターは、Kotlinの公式マスコットです。

絶妙に可愛くないですが、私は好きです。

KMM (Kotlin Multiplatform Mobile) チュートリアル

参考: Kotlin のマスコットが登場! | The Kotlin Blog