7月27・28日、サイバーエージェントの次世代技術者による技術カンファレンス「CA BASE NEXT 2022」を開催しました。本記事では「Kotlin Multiplatform MobileのおさらいとABEMAでのマルチプラットフォーム対応」についてご紹介します。
開催当時の内容はCA BASE NEXT 2022 公式サイトからご覧いただけます。
近年、技術的なトレンドとしてKMM(Kotlin Multiplatform Mobile)やFlutter、React Nativeといったマルチプラットフォーム、クロスプラットフォーム技術が注目を浴びています。 Kotlinは2018年にリリースしたKotlin 1.3から、AndroidとiOSを含む様々なプラットフォーム間でのコードの共通化が可能になり、今年(2022年)にはKMMのベータ版リリースが予定されています。 様々なプラットフォームにサービスを展開しているABEMAでも、このKMMによってプラットフォーム間でのコードの共通化に取り組んでいます。 この記事では、特にモバイルプラットフォームにフォーカスを当ててKotlin Multiplatformのこれまでを振り返りつつ、ABEMAにおけるKMMの活用事例や開発している中での様々な知見を紹介させていただきます。また、セッションで発表した内容に加えて、その後アップデートされた情報も併せて紹介します。
目次
- Kotlin Multiplatform Mobile (KMM) とは
- Kotlin Multiplatformのこれまで
- Koltin Multiplatformの仕組み
- チームで導入・運用していくには
- KMMの課題と将来性
- KMM Betaに向けて
- おわりに
Kotlin Multiplatform Mobile (KMM) とは
Kotlin Multiplatform?
Kotlin Multiplatform Mobile (KMM)を説明する前に、Kotlin Multiplatform (KMP)について説明します。Kotlin Mutiplatformとは、JetBrains社によってオープンソースで開発されているプログラミング言語であるKotlinを利用して、複数プラットフォームに対応したアプリケーションを開発する技術です。
https://kotlinlang.org
Kotlin Multiplatform (KMP)とは、Kotlinを利用して複数プラットフォームに対応したアプリケーションを開発する技術やそのエコシステム全体を指します。
そして、Kotlin Multiplatform Project (Kotlin MPP)は、Kotlin Multiplatformを用いて開発されるプロジェクトのことを指します。
では、Kotlin Multiplatform Mobile (KMM)とは何か。公式では、SDK for iOS and Android app developmentと説明されています。Kotlin Multiplatformに加えて、いくつかのモバイルアプリケーション開発に特化した技術を総称して、Kotlin Multiplatform Mobileと呼んでいます。
It’s Time for Kotlin Multiplatform Mobile by Ekaterina Petrova
Kotlin MultiplatformやKMMのメリット
次に、Kotlin MultiplatformやKMMを利用することで、どのようなメリットがあるかをご紹介します。
まず第一に、Kotlinの強力なAPIを活用してソースコードを共通化できることです。Kotlinの優れた言語機能を利用して開発できることは大きなメリットです。GoogleがAndroid開発におけるプログラミング言語としての公式サポートを発表してから5年以上が経ち、実績としても十分です。
次に、プラットフォーム特有のAPIもほとんどが利用可能です。例えば、iOS特有のAPIであるNSUserDefaultsをKotlinから参照し、実装することができます。また、Compose Multiplatformによって、デスクトップアプリやウェブアプリのUI実装までKotlinで行うことが可能です。Kotlin Multiplatformで開発できるプラットフォームの数の多さも魅力の一つです。
さらに、HTTP通信(Ktor)、シリアライズ(kotlinx.serialization)、非同期処理(Kotlin Coroutines)といったモバイルアプリケーション開発に必要不可欠な機能をJetBrainsが提供・メンテナンスしていることも心強いです。もちろん、サードパーティーの優れたライブラリも数多く存在しています。
最後に、Kotlin Multiplatformはシステムの一部から導入することができることも大きなメリットです。例えば、私が所属しているABEMAでは、まずデータ層のAPI通信周りをKotlin Multiplatformによって共通化しました。少しずつ導入を進めることができるため、チームの状況に併せて導入を進めやすく、変化にも対応しやすいです。Kotlin Multiplatformによる実装は、ライブラリとしても配布することが可能です。既存コードへのインテグレーション方法には多くの選択肢が用意されています。
FlutterやUnityなどとの違いは?
マルチプラットフォーム、クロスプラットフォームアプリケーション開発には、FlutterやUnity、.NET MAUIなど数多くの選択肢が存在します。
これらとKotlin Multiplatformの大きな違いは、特定の実行環境を必要としない点です。Kotlin Multiplatformで実装されたソースコードは、コンパイルの時点で各プラットフォームのNativeコードに変換されます。モバイルアプリケーションではUIパフォーマンスはアプリケーションの重要な指標です。特定の実行環境を必要とせず、Native環境で動作することは大きなメリットです。
一方で、Kotlin Multiplatformは現時点でiOS、tvOS、watchOSのUIまでの共通化を公式サポートしていません。(検証は行われているようなので後ほど紹介します)
Kotlin Multiplatformのこれまで
Kotlinのバージョンと共にKotlin Multiplatformの歴史を振り返ります。
そして、iOSを含むNativeプラットフォームとのソースコードの共通化が可能となるKotlin/Native BetaがKotlin 1.3でリリースされ、Kotlin 1.4ではKMM Alphaが発表されました。実はKotlin/Nativeは元々Kotlinと別プロジェクトとして管理されていましたが、1.5あたりからKotlinと同じプロジェクト内で管理されるようになりました。
2021年に開催されたKotlin 2021 Premier Online EventのKeynoteにおいて、KMM Betaを2022年の春にリリースする計画が発表されました。
Kotlin 2021 Premier Online Event Keynote
Kotlin Multiplatform Mobile Beta Roadmap
そのほかのRoadmapやKotlinの各コンポーネントのステータスは、公式のホームページをご確認ください。
Koltin Multiplatformの仕組み
Kotlinのエコシステムは、プラットフォーム共通のライブラリやフレームワーク、ツール、コンパイラによって構成されています。これに依存する形で各プラットフォーム固有のAPI(Kotlin/JVM、Kotlin/JS、Kotlin/Wasm、Kotlin/Native)が存在しています。
Kotlin 2021 Premier Online Event Keynote
Kotlin 2021 Premier Online Event Keynote
ソースコードの共通化
Kotlin Multiplatformではcommonソースセットにプラットフォーム共通の実装をおくことができます。特定のプラットフォームに特化したソースセットを作ることもでき、似たようなプラットフォームをまとめたソースセットの定義も可能です。
multiplatform.html
multiplatform.html
expect/actual
Kotlin Multiplatformの仕組みを見ると、commonのソースセットでinterfaceを定義し、各プラットフォームのソースセットで実装を行うということをやりたくなるかもしれません。これを実現するために、Kotlin Multiplatformではexpect/actualという修飾子が用意されています。
https://kotlinlang.org/docs/multiplatform-connect-to-apis.html
また、commonソースセットだけでなく、環境の近いプラットフォームをまとめたソースセット(例えばiosMain)でもexpectを用いた定義を行えます。
Kotlin Multiplatformが対応しているプラットフォーム
Kotlin MultiplatformはJVM、Android、iOS、JSをはじめ、数多くのプラットフォームに向けた実装を行うことが可能です。
プロジェクトの構成と設定
では、実際のプロジェクト構造とKotlin Multiplatformに対応するための設定を見ていきましょう。
今回はAndroidとiOSを対象プラットフォームとして説明しますが、他のプラットフォームについても同様に構成を設定することができます。
そして、そのモジュールのbuild.gradle.ktsにBuild設定を記述します。
まず、このモジュールで対象とするプラットフォームを定義します。ここでは各プラットフォームに対して、追加のGradleタスクやアーティファクトの設定を行うことができます。
kotlin {
android()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "base"
}
}
// ...
}
次に、ソースセット間の依存関係やライブラリの依存を追加します。dependsOnを使用することで、親の依存を継承することが可能です。ただし、親で依存を追加しているライブラリ等がそのプラットフォームに対応している必要があります。
もし、ソースセットの名前を独自に定義したい場合や、複数プラットフォームをまとめたソースセットを作成したい場合にも、ここで設定します。
kotlin {
// ...
sourceSets {
val commonMain by getting {
dependencies {
// ライブラリやモジュールの依存を追加する
}
}
val commonTest by getting
val androidMain by getting
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
また、複数ターゲットをまとめたショートカットも用意されています。
https://github.com/JetBrains/kotlin/blob/v1.7.20-Beta/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetContainerWithNativeShortcuts.kt
- ios (iosArm64/iosX64)
- tvos (tvosArm64/tvosX64)
- watchos (watchosArm32/watchosArm64/watchosX64)
現時点で、Apple Silicon搭載のデバイス上で動作するシミュレーター向けのxxxSimulatorArm64はこれらのショートカットに含まれていないため、注意してください。
チームで導入・運用していくには
ここまでKotlin Multiplatformの歴史とその仕組みについて解説しました。ここでは実際にチームで導入・運用するために助けとなるリソースの紹介をします。
まずは、KotlinやKotlin Multiplatformについて知ることが重要です。Kotlinを開発しているJetBrains社が多くのリソースやコミュニティを用意しています。
- 公式ドキュメント
- 定常的にメンテナンスが行われており、最近もKMMのGet Startedページが更新されています
- ドキュメントはこちらで管理されています
- 公式ブログ
- Kotlinにまつわる新しいリリースやコミュニティに関する情報がまとまっています
- YouTubeチャンネル
- KotlinのTipsや新しい言語機能の紹介などの動画が提供されています
- イベントが開催された際にはその動画もこちらに公開されます
- Slack
- Kotlinを開発しているJetBrains社のデベロッパーやコミュニティのデベロッパーとコミュニケーションを取れる場です
- SlackのWorkspace自体は招待制ですが、メンバーにならずともWebサイト上でメッセージを閲覧できるようになりました
- Twitter
- 最新のリリースやブログやYouTubeの更新などの情報が発信される場です
- GitHub
- Kotlinのソースコードが管理されています
- オープンソースなので誰でもコントリビュートすることができます
- YouTrack
- KotlinやJetBrains社が開発しているIDEなどのIssueが管理されています
- 開発の進捗状況やRoadmapのチケットもこちらで管理されています
- Kotlin/KEEP
- Kotlinの言語機能に関するプロポーザルが管理されています
- どのような思想で言語機能が作られたかやユースケースなどを閲覧することができます
- IssueやPRで新しい言語機能に関する議論が活発に行われているので読み物としても非常に面白いです
- ハンズオン
- 手を動かしながらアプリケーションや簡単なサンプルを通してKotlinの機能を学習することがきます
この他にも、RedditやStackOverflowなどでも情報を得ることができます。
また、サンプルプロジェクトやコミュニティが作成しているプロジェクトを通してより実践的なKotlin Multiplatformの活用方法を学ぶことができます。ここでもそれらのいくつかをご紹介します。
- Kotlin Multiplatform Mobile Sample
- 公式がメンテナンスしているKMMのシンプルなサンプルです
- まずはここを見るのがいいでしょう
- KMM RSS Reader
- 公式がメンテナンスしているKMMのサンプルです
- ネットワーク通信や永続化の実装も含まれます
- KaMP Kit
- KMMによるモバイルアプリケーション開発を支援しているTouchlab社がメンテナンスしているサンプルです
- より実践的なKMMの活用方法やTouchlab社が提供している他のツールのサンプルとしても参考になります
- Sessionize/Droidcon Mobile Clients
- Touchlab社を中心に作られたDroidcon NYCのセッション情報のアプリケーションです
- Composeを利用してiOSのUIの共通化までチャレンジしています
- DroidKaigi 2022 official app
- 日本のAndroid界隈の大きなイベントであるDroidKaigiのカンファレンスアプリケーションです
- データ層をKMMで共通化しています
- 弊社のエンジニアであり、GDEでもある毛受がメインコントリビューターとして貢献しています
どのように導入を進めるか
Kotlin Multiplatformの導入を検討するにあたり、既存のプロジェクトであれば既存のソースコードにどう適用するか、どこから導入していくか、新規のプロジェクトであればどこまで共通化するかなどを考える必要があります。
この他にも次の点を導入前の検討項目として挙げていくことをお勧めします。
- プロジェクトの管理方法
- 複数プラットフォーム向けのソースコードをモノリポジトリで管理するか、別々のリポジトリで管理するかを検討してください
- いずれの場合でも、Kotlin Multiplatformによって生成した成果物を各リポジトリでどのように参照するかを検討してください
- 共通コードをiOSで参照する方法は、ビルドスクリプトを介する方法とCocoaPodsを介する方法が用意されています
- どこを共通化するか、どこから共通化するか
- Kotlin Multiplatformによってどこの実装を共通化するか、どこから共通化するかを検討してください
- 共通化する部分がSDKやライブラリに依存している場合は、それらがKotlin Multiplatformに対応しているか確認してください (対応していない場合は、インタフェースのみを共通化する方針も取れるかと思います)
- 共通化する層がUIに近くなるほどプラットフォーム差異の影響を受けやすくなるため、プラットフォームのライフサイクルに影響をうけるロジックはDecomposeなどの導入を検討してみてください
- アーキテクチャやプロジェクトの構成
- Kotlin Multiplatformによってどこを共通化するかによって変化する部分でもありますが、共通化部分のモジュール分割の単位、依存関係といったアーキテクチャを検討してください
- Kyashさんのイベントでのお話が非常に参考になると思います
- ライブラリやツールの選定
- 先でも触れましたがモバイルアプリケーションの実装にはHTTP通信、永続化、シリアライズ、非同期処理、ロギング、デバッグといった技術が必要不可欠です
- それらを抽象化してより簡潔に扱うことのできるライブラリやツールが数多く存在しているため、多角的な観点から選定を行なってください
-
Kotlin Multiplatform関連でよく使われるライブラリやツール
- HTTP通信
- 永続化
- シリアライズ
- 非同期処理
- ロギング
- デバッグ
- 日付・時刻
- BLoC
- テスト
- API Docs
- Build
- 環境構築
私の所属しているABMEAで、Kotlin Multiplatformをどのように導入していったかやアーキテクチャ、プロジェクトの構成などは他のイベントでお話ししていますので、併せてご覧下さい。
チームを説得し導入を進める
Kotlin Multiplatformの導入はゼロコストで進められるという訳でありません。技術的な検証と並行して、Kotlin Multiplatformの導入のためにチームメンバーや経営陣を説得し、時間と人的リソースを確保する必要があります。
しかし、新しい技術の導入にあたる工数の算出は非常に難しいと思います。また、実際に導入を進めることで明らかになる障壁や課題もあるでしょう。
JetBrains社はCase Studiesとして世界の企業のKMMの導入事例を紹介しています。チームを説得する材料や導入前のリスクの洗い出しなどにおいて非常に参考になるでしょう。
ABEMAではドキュメントの整備や共有会を通して、Kotlin Multiplatformへの参入障壁を下げる取り組みを行なっています。
KMMの課題と将来性
KMMはBeta目前というステータスですが、KMMにはまだまだ多くの課題も残されています。
UI実装の共通化
これを課題と言うかは意見が分かれるところかと思いますが、他のマルチプラットフォーム、クロスプラットフォームの技術はUI実装の共通化まで行えるものが多いです。Kotlin MultiplatformもCompose MultiplatformによってAndroid、Web、Desktopアプリケーションで実装を共通化することができます。しかし、これはiOS、watchOS、tvOSといったAppleの一部プラットフォームにはまだ公式に対応されていません。
一方、世界中のデベロッパーや企業がKotlin Multiplatformを活用したAndroid、iOSのUI実装共通化に取り組んでいます。たとえば、先日行われたDroidCon NYCでは、RedwoodというUIライブラリの発表がありました。また、JetBrains社もCompose Multiplatformのプロジェクト内でiOS向けの対応を検証していたり、GoogleがAndroidXのいくつかのライブラリにiOSをターゲットとして追加したりしています。
Kotlin Multiplatform、KMMにおけるUI実装の共通化は今後も動向をチェックしていきたいトピックです。
Compose MultiplatformのiOS対応について、別のイベントでもお話ししていますので、ぜひご覧ください。
Swiftとの互換性
iOS向けのバイナリは、Kotlinの Compiler Frontendを介した後に、LLVMでObjective-Cに変換され生成されます。SwiftからObjective-Cの実装を呼び出すことができるため、KMMの実装を参照することができます。Kotlin、Objective-C、Swiftは異なるプログラミング言語で、もちろん言語仕様も異なります。つまり、KMMの実装の互換性を完全に保つことは難しいです。よって、iOSからKMMの実装を利用しやすくするためにワークアラウンドが必要になることもあります。
KotlinとSwiftは比較的新しい言語のため、言語仕様が似ていることもあり、KotlinからSwiftへの変換を望む声が多くあります。JetBrains社も対応を検討していましたが、一度ロードマップから外れることとなりました。今後、問題が解消され対応が再開されることを願っています。
KotlinとSwift/Objective-Cとの互換性については公式のドキュメントをご覧ください。
ビルド時間
Kotlin/Nativeのビルド時間が長いという課題は以前からあり、Kotlin 1.5以降でJetBrains社も注力して取り組んでいるように思われます。以前に比べてコンパイル時間やアーティファクト生成にかかる時間は短縮されていますが、まだ長いのが現状です。キャッシュの改善やインクリメンタルコンパイルによって、今後も改善されることを期待しています。
ABEMAでは、Dropbox社が提供しているAffected Module DetectorをKotlin Multiplatformでも扱えるようにしています。変更影響のあるモジュールだけをコンパイル、テストすることで、ビルド時間の短縮に取り組んでいます。
KKMM Betaに向けて
Hierarchical Project Structure
Kotlin 1.4.0から導入されたHierarchical Project Structureにより、複数プラットフォームのソースセットをまとめることが可能になりました。
設定を有効にするには、プロジェクトルートのgradle.propertiesに設定を記述します。
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
プロジェクトをHierarchical Project Structureに移行するためには、依存しているライブラリもHierarchical Project Structureに対応していなければなりません。
multiplatform-hierarchy.html
また、Hierarchical Project Structureを無効にすることも可能です。
kotlin.mpp.hierarchicalStructureSupport=false
Hierarchical Project Structureでどう変わるかや生成されるバイナリの変化などはこちらで詳しく解説されています。
Kotlin/Native Memory Management
Kotlin/Nativeにおいてマルチスレッドプログラミングを行う際のルールとして次の2つがあります。
- Mutable State == 1 thread
- Immutable State == many thread
ここでいうImmutable Stateとはvalで定義されたプロパティのようにコンパイル時点で静的に不変であると判定できるものを指しません。Kotlin/Nativeにはfreeze()という関数が提供されており、複数スレッドで扱いたいインスタンスをランタイムで不変なもの(frozen state)としてマーキングすることができます。このfreeze()関数によってfrozen stateになったインスタンスに限り、複数スレッドで扱うことが可能です。
しかし、変更可能な状態をシングルスレッドのみに制約した上で開発を行うことはあまり現実的とはいえません。解決策として、AtomicXxxを使用する方法とThread-isolated Stateを使う方法が存在します。これらの使い分けですが、シンプルな値といったそれほど大きくないオブジェクトについては、AtomicReferenceなどのAtomicXxxが扱いやすいです。しかし、値の操作にコストがかかる場合やコレクションにおいては、Thread-isolated stateを選択するのがいいでしょう。 また、Thread-isolated stateはAtomicReferenceで起こり得る値の不整合を防ぐことができます。 これは、値の操作が、スケジュールされた単一のスレッドで行われるため、複数の操作が発生しても値の一貫性が保証されるためです。
https://kotlinlang.org/docs/multiplatform-mobile-concurrent-mutability.html
AtomicXxxやThread-isolated Stateをより簡潔に扱うために、Kotlin/kotlinx.atomicfuやtouchlab/Statelyといったライブラリを利用することをお勧めします。
これらのライブラリがあるとはいえ、Kotlin/Nativeのために対応が必要になるのは開発体験としてあまりよくありません。そこで、JetBrainsはKotlin/Native New Memory Managementというメモリ管理の再開発に取り組んでいます。KMM Betaの延期はこのNew Memory Managerの安定性を高めることが一つの要因として挙げられていました。
New Memory Managementによって次の点が変わります。
- Reference-counting GC -> Tracing GC (※ 今後変わる可能性もあり)
- 並行でのGCが可能に
- freeze()が不要になる
- @SharedImmutableなしにトップレベルプロパティの参照、変更が可能に
- AtomicReferenceを含んだ循環参照によるメモリリークが発生しない
このNew Memory ManagementはKotlin 1.6.20からexperimentalとして利用可能で、1.7.0ではalphaというステータスになっています。また、Kotlin 1.7.20ではBetaとなり、デフォルトでNew Memory Managementが有効になりました。
いくつかのライブラリではすでにNew Memory Managementへの対応を進めています。kotlinx.coroutinesは、1.6.0以降のバージョンでnative-mtバージョンのアーティファクトを利用しなくてよくなっています。ktorは2.0.0以降のバージョンを利用するには、プロジェクト側がNew Memory Managementに移行している必要があります。
プロジェクトのNew Memory Managementへの移行の際にはこちらのマイグレーションガイドをご覧ください。
おわりに
Kotlin MultiplatformやKMMのこれまでを振り返りながら、プロジェクトへの導入に関するノウハウやKMMの課題や将来性についてご紹介しました。Kotlin Multiplatformはまだまだ成長段階のフェーズですが、これからも進化を続け、より安定した便利な技術になっていくと期待しています。
ぜひ皆さんのプロジェクトでもKotlin Multiplatformの導入を検討してみてください。
■「CA BASE NEXT 2022」のアーカイブ動画・登壇資料は公式サイトにて公開しています。ぜひご覧ください。
https://ca-base-next.cyberagent.co.jp/2022/
■ 採用情報
サイバーエージェントに少しでも興味を持っていただきましたら、お気軽にマイページ登録やエントリーをお願いします。
◆ ABEMAエンジニア採用
◆ 中途採用
◆ 新卒エンジニア採用
◆ 新卒クリエイター採用