KINTO Tech Blog
General

Kotlin Multiplatform Mobile (KMM)を使ったモバイルアプリ開発

Yao Xie, Mooseok Bahng
Yao Xie, Mooseok Bahng
Cover Image for Kotlin Multiplatform Mobile (KMM)を使ったモバイルアプリ開発

はじめに

KINTOテクノロジーズでグローバルグループのモバイルアプリ開発を担当している、謝堯(Yao Xie)、方茂碩(Mooseok Bahng)です。

現在、Global KINTO App というアプリの開発を担当しています。Global KINTO App (GKA)は「世界中のKINTOサービスを1つのアプリでつなぐ」というコンセプトを持たせたモバイルアプリです。現時点ではタイとカタールのKINTOサービスが実装されています。

既存のアプリのかわりになるプロジェクトを進めている中で、Kotlin Multiplatform Mobile (以下 KMM)の導入を決めましたので、今回お話します。

KMMの導入を決めた背景

KMMの導入を決めた背景には次のような課題がありました。

  • どうしてもiOSとAndroidの間に Business Logicの実装の差が発生する。
  • 開発チームが物理的に2ヶ所に別れていて、開発の効率が落ちる場合がある。-> チームを KMMとNativeに分けることで改善できると思った。
  • 開発リソースが限られているため、効率的な開発体制を構築したい。

その結果、KMMを検討することになりました。

Kotlin Multiplatform Mobile (KMM)とは

KMMは iOS, Androidアプリを開発するためのSDKで、基本言語は Kotlinで、Cross-Platformと Nativeアプリのいいところ取りをしています。
KMMで共通のビジネスロジックを開発して、プラットフォーム依存性がある UIはそれぞれ Nativeで開発できます。

個人的にはそれぞれのOSに最適なUIを提供することが一番いいUI/UXだと思っています。
KMMはUIは基本それぞれの Nativeで開発するため、KMMはUIは基本それぞれの Nativeで開発するため、UI/UXの最適化ができ、iOS, Androidとの依存性が少なく、バージョンアップの影響もほとんどないと思っています。

KMMはまだまだ新しい技術で、十分に成熟してないですが、近年いろいろな会社で使われている状況です。

KMM

Kotlin Multiplatform Mobile (source: https://kotlinlang.org/lp/mobile)

Architecture

KMMについて説明する前に、現在開発チームで採用しているアーキテクチャーについて説明します。
基本的にMVVMを採用して開発しています。KMMを導入しても基本この方針には変更はありません。

MVVM Architecture

ここで、どこまでKMMに含めるかが悩みでした。選択肢は三つぐらい考えられます。

KMM Native
Option 1 Repository, Usecase, View Model UI
Option 2 Repository, Usecase View Model, UI
Option 3 Repository Usecase, View Model, UI

色々試してみましたが、今のところはView ModelまでKMMにする方向に進んでいます。View Modelは除外することも検討してみましたが、せっかくKMMを導入したのに View Modelを別々にする理由が見つかりませんでした。データをリストに表示するだけなどの簡単なフィーチャーについては特にそうです。
これから複雑な機能が追加されると、View Modelを別にする必要があるかもしれません。その時は一部だけView Modelを別々にしたりすることも考えられます。

KMM Architecture

iOSのCodebaseはかなりコンパクトになりました。Domain LayerとView ModelまでKMMを使っているので、UIとプラットフォーム依存のあるハードウェア関連の機能だけになり、ソースコードの量は半分以下になるのではと思っています。。

下記はFAQリストを表示する簡単な画面のiOS側の実装になります。共通のUI Utility Classを除くとこれで終わりです。

struct FaqView: View {
    
    private let viewModel = FaqViewModel()
    @State var state: FaqContractState
    
    init() {
        state = viewModel.createInitialState()
    }
    
    var body: some View {
        NavigationView {
            listView()
        }
        .onAppear {
            viewModel.uiState.collect(collector: Collector<FaqContractState> {
                self.state = $0
            }
            ) { possibleError in
                print("finished with possible error")
            }
        }
    }
    
    private func listView() -> AnyView {        
        manageResourceState(
            resourceState: state.uiState,
            successView: { data in
                guard let list = data as? [Faq] else {
                    return AnyView(Text("error"))
                }
                return AnyView(
                    List {
                        ForEach(list, id: \.self) { item in
                            Text(item.description)
                        }
                    }
                )
            },
            onTryAgain: {
                viewModel.setEvent(event: FaqContractEvent.Retry())
            },
            onCheckAgain: {
                viewModel.setEvent(event: FaqContractEvent.Retry())
            }
        )
    }
}

メリット

  • Single Codebase

iOS, Androidのネットワーキング、データストレージ、ビジネスロジックなどを Single Codebaseで管理することができます。

  • Consistency

ビジネスロジックを共有するため、基本的に同じUXを提供することができます。

  • Efficiency

KMMの導入によって効率的な開発ができるようになりました。半分に近いタイムコストをカットすることによって、その分、ソースコードの最適化やビジネスの展開に時間を使うことができるようになりました。

  • Expandable

iOS, Androidだけではなく、必要によっては他のプラットフォームにも簡単に拡張することができます。

Expandability

デメリット

  • iOSのデバッグは別途のpluginのインストールが必要となります。
  • XCframeworkを適用する場合、Apple Siliconの Macで Simulatorを使うとarm64を参照するため、エラーになります。これは KMM SDK側の修正が必要だと思いますが、暫定的には excluded architectureに arm64を追加するか、x-codeを rosettaモードで実行すると simulatorも使えるようになります。

iOSへの配布方法

Build & Sourcesets

sourceset

Build XCFrameworks

KMMの今までのiOSへの配布は Universal (FAT) frameworkが基本でした。最近、やっと公式にXCFrameworkをサポートすることになりましたので、XCFrameworkを採用する予定です。

https://kotlinlang.org/docs/multiplatform-build-native-binaries.html#build-xcframeworks

その他

KMMと直接関係はないですが、下記の新しい技術も導入を検討しています。

Ktor

  • 両方のClientに対して同じConfigurationをセットすることができます。
  • API Requestコードは共通です。
  • iOS, Androidのエンジンは別々ですが、追加のコードはいらないです。

Ktor

Apollo Client

既存のプロジェクトではGraphQL APIも一部採用しています。GraphQLのために Apollo Clientの導入を検討しています。
Backendの schema.graphqlsを使い、Queries.graphqlを作成するだけで、Models, Adapters, Queriesが自動生成されます。

https://github.com/apollographql/apollo-kotlin

MMKV

MMKVはモバイル key-value storage frameworkです。もちろん、Android, iOSを含めて、マルチプラットフォームに対応しています。

https://github.com/Tencent/MMKV
https://github.com/ctripcorp/mmkv-kotlin

MMKV-Kotlinで MMKVを簡単に私たちのプルジェクトにインテグレートすることができ、shared moduleで key-value storageを管理することができるようになります。

Performance comparison on Android

MMKV_vs_SP

Performance comparison on iOS

MMKV_vs_NSUserDefaults

今後の展開

Fast growing KMM ecosystems

KMMは JetBrainsによって開発されています。Android Studioでシームレスに開発でき、Xcodeも一部対応しています。
開発者の中で広がってきており、KMMのためのオープンソースライブラリがたくさん存在します。

https://github.com/terrakok/kmm-awesome

Cross-platform UI

Touchlabはすでに Compose UIを iOS、Android両方に使う実験を開始しています。

https://touchlab.co/compose-ui-for-ios/

@Composable
internal actual fun PlatformSpecificSettingsView(viewModel: SettingsViewModel) {
    IconTextSwitchRow(
        text = "Use compose for iOS",
        image = Icons.Default.Aod,
        checked = viewModel.observeUseCompose,
    )

    Divider()
}

近いうちにKMMは公式に cross-platform UIをサポートするようになるかもしれません。

まとめ

ここまでKMMの導入について説明しました。
KMMの導入によって下記の改善が期待できるようになりました。

  • iOSとAndroidの間のビジネスロジックの実装の差を最小限に抑制することができる。
  • 必要によっては、開発チームの構成の最適化ができる。
  • ある程度開発工数を減らすことができる。

まだまだKMM導入の初期段階なので、課題にたくさん直面し、どんどんノウハウが蓄積するので、進捗があり次第また共有します。

ありがとうございました。

Facebook

関連記事 | Related Posts

We are hiring!

【iOS/Androidエンジニア】モバイルアプリ開発G/東京・大阪

モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。

【部長・部長候補】/プラットフォーム開発部/東京

プラットフォーム開発部 について共通サービス開発GWebサービスやモバイルアプリの開発において、必要となる共通機能=会員プラットフォームや決済プラットフォームの開発を手がけるグループです。KINTOの名前が付くサービスやTFS関連のサービスをひとつのアカウントで利用できるよう、様々な共通機能を構築することを目的としています。