Slide 1

Slide 1 text

Android開発者のための Kotlin Multiplatform入門 2024-06-29 長澤 太郎

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

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") } } Button(onClick = { showContent = !showContent }) { Text("Click me!") } AnimatedVisibility(showContent, enter = scaleIn(), exit = scaleOut()) { val greeting = remember { Greeting().greet() } Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { Image(painterResource(Res.drawable.compose_multiplatform), null) Text("Compose: $greeting") } }

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

自己紹介 長澤 太郎 @ngsw_taro Kotlin Fest 代表 / Ubie株式会社 自称Kotlinエバンジェリスト 著書・講演など多数 Kotlin技術アドバイザーなど数社

Slide 6

Slide 6 text

1. Kotlinの復習  ・Kotlinの登場  ・Android開発における採用  ・Kotlinの便利なところ 2. AndroidのGUIプログラミングの歴史  ・初期  ・MVP  ・Flux  ・MVVM  ・Jetpack Compose 3. 最新のKotlin事情  ・Kotlin 2.0  ・Kotlin Multiplatform  ・Compose Multiplatform  ・コードを交えた実装例    ・Fleet    ・Android, iOS

Slide 7

Slide 7 text

1. Kotlinの復習  ・Kotlinの登場  ・Android開発における採用  ・Kotlinの便利なところ 2. AndroidのGUIプログラミングの歴史  ・初期  ・MVP  ・Flux  ・MVVM  ・Jetpack Compose 3. 最新のKotlin事情  ・Kotlin 2.0  ・Kotlin Multiplatform  ・Compose Multiplatform  ・コードを交えた実装例    ・Fleet    ・Android, iOS

Slide 8

Slide 8 text

Kotlinの登場 ● 2011年7月 JVM Language Summitにて、開発中であることが発表された ● IntelliJ IDEAなどの統合開発環境を開発・提供するJetBrains社 ● 当時のJava言語の抱える課題に対して、Better Javaとして登場 ○ Java仮想マシン(Java Virtual Machine)の上で走る言語 ● JVMだけでなく、JavaScriptへのトランスパイルも視野に入れていた ● 2016年にバージョン1.0がリリース

Slide 9

Slide 9 text

Android開発における採用 ● KotlinはAndroid開発には最適な言語だった ● 当時のAndroid Javaは、Java SE 7に相当する言語機能だった ○ コレクション操作やOptional型: 代わりに、GuavaやLightweight Stream APIを使った ○ ラムダ式: 代わりに、Retrolambdaを使った List nameLengths = new ArrayList<>(); for (String name: names) { nameLengths.add(name.length()); } List nameLengths = Stream.of(names) .map(name -> name.length()) .toList();

Slide 10

Slide 10 text

Android開発における採用 ● Kotlinは、上記の問題をすべてクリアしていた ● 2017年、Androidの開発言語としてKotlinが正式サポートされた ● KotlinはAndroid開発には最適な言語だった ● 当時のAndroid Javaは、Java SE 7に相当する言語機能だった ○ コレクション操作やOptional型: 代わりに、GuavaやLightweight Stream APIを使った ○ ラムダ式: 代わりに、Retrolambdaを使った

Slide 11

Slide 11 text

Kotlinの便利なところ ● Better Javaとして過不足なし ● やや独特なところはあるが、文法や思想に大きなジャンプはない ● ラムダ式があり、それを活かしたコレクション操作がある ● 静的型付け + 型推論 ● null安全 ● 拡張関数 ● プロパティ

Slide 12

Slide 12 text

ラムダ式とコレクション操作 Project Euler Problem 1「1000未満の3か5の倍数となる数の合計」 var sum: Int = 0 for (var i: Int = 0; i < 1000; i++) { if (i % 3 != 0) continue if (i % 5 != 0) continue sum += i } println(sum) val sum: Int = (0 until 1000) .filter { it % 3 == 0 || it % 5 == 0 } .sum() println(sum) ※擬似コードです Kotlinでは、このようなforループは提供されていません ラムダ式

Slide 13

Slide 13 text

静的型付けと型推論 ● 静的型付け: コンパイル時に、データ型の整合性を確認する ● 型推論: 文脈から式の型を推論することで、型の明示的な記述を省略できる val list: MutableList = mutableListOf(1, 2) list.add(3) list.add("4") // 型の不一致によるコンパイルエラー val list = mutableListOf(1, 2) list.add(3) list.add("4") // 型の不一致によるコンパイルエラー

Slide 14

Slide 14 text

null安全 ● 式がnullである可能性が、あるかないかを厳格に区別する ● 式がnullである可能性がある場合、メンバーの呼び出しが制限される val name: String = "Kotlin" println(name.length) val color: String? = "Red" println(color.length) // コンパイルエラー if (color != null) { println(color.length) // OK } println(color?.length) // OK

Slide 15

Slide 15 text

拡張関数 ● 型を変更せずに、メソッドを追加しているように見せる機能 fun hello(name: String): String { return "Hello, $name!" } fun main() { val message = hello("World") println(message) // Hello, World! } fun String.hello(): String { return "Hello, $this!" } fun main() { val message = "World".hello() println(message) // Hello, World! }

Slide 16

Slide 16 text

プロパティ ● オブジェクトのデータを表現するインタフェース ● つまり、データの実体がメモリに格納されているなどの詳細は不問 ● デフォルトでは、データの実体はメモリに格納される class Name(val value: String) { val length: Int = value.length } fun main() { val name = Name("Kotlin") val len = name.length println(len) } class Name(val value: String) { val length: Int get() { return value.length() } }

Slide 17

Slide 17 text

1. Kotlinの復習  ・Kotlinの登場  ・Android開発における採用  ・Kotlinの便利なところ 2. AndroidのGUIプログラミングの歴史  ・初期  ・MVP  ・Flux  ・MVVM  ・Jetpack Compose 3. 最新のKotlin事情  ・Kotlin 2.0  ・Kotlin Multiplatform  ・Compose Multiplatform  ・コードを交えた実装例    ・Fleet    ・Android, iOS

Slide 18

Slide 18 text

1. Kotlinの復習  ・Kotlinの登場  ・Android開発における採用  ・Kotlinの便利なところ 2. AndroidのGUIプログラミングの歴史  ・初期  ・MVP  ・Flux  ・MVVM  ・Jetpack Compose 3. 最新のKotlin事情  ・Kotlin 2.0  ・Kotlin Multiplatform  ・Compose Multiplatform  ・コードを交えた実装例    ・Fleet    ・Android, iOS

Slide 19

Slide 19 text

初期 class MainActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) val textView = findViewById(R.id.text_view) as? TextView val button = findViewById(R.id.button) as? Button button?.setOnClickListener { textView?.setText("Clicked") } } }

Slide 20

Slide 20 text

MVP (Model, View, Presenter) Activityとか <> View <> Presenter <> Model PresenterImpl ModelImpl ● Model = 業務ロジック, View = 表示, Presenter = ModelとViewの紐付け ● 結合が疎になるので、テスタビリティ、メンテナビリティが向上 矢印の先に、依存している(呼び出している) 矢印の先を実装している

Slide 21

Slide 21 text

Flux ● 単方向データフローに基づいた設計パターン ● データの流れが決まっているので、推測しやすい View Dispatcher Store データの流れ Action Action State

Slide 22

Slide 22 text

MVVM (Model, View, ViewModel) ● ViewModel = UIロジック、Model呼び出し、Viewへの更新通知 ● Android Architecture Componentsにより実装が容易 View ViewModel Model データの流れ DataBinding / LiveData

Slide 23

Slide 23 text

Jetpack Compose ● Kotlinによる宣言型UIツールキット ● 考え方: 現在の状態から、現在のUIへと変換をする @Composable fun Greeting(name: String) { Text(text = "Hello $name!") } @Composable fun GreetingList(names: List, modifier: Modifier) { Column(modifier = modifier) { names.forEach { name -> Greeting(name = name) } } }

Slide 24

Slide 24 text

1. Kotlinの復習  ・Kotlinの登場  ・Android開発における採用  ・Kotlinの便利なところ 2. AndroidのGUIプログラミングの歴史  ・初期  ・MVP  ・Flux  ・MVVM  ・Jetpack Compose 3. 最新のKotlin事情  ・Kotlin 2.0  ・Kotlin Multiplatform  ・Compose Multiplatform  ・コードを交えた実装例    ・Fleet    ・Android, iOS

Slide 25

Slide 25 text

1. Kotlinの復習  ・Kotlinの登場  ・Android開発における採用  ・Kotlinの便利なところ 2. AndroidのGUIプログラミングの歴史  ・初期  ・MVP  ・Flux  ・MVVM  ・Jetpack Compose 3. 最新のKotlin事情  ・Kotlin 2.0  ・Kotlin Multiplatform  ・Compose Multiplatform  ・コードを交えた実装例    ・Fleet    ・Android, iOS

Slide 26

Slide 26 text

Kotlin 2.0 ● K2コンパイラが安定版に。従来の2倍の速度 ● 文法の変更は、なし ● バージョン2.1以降で、文法の変更・言語機能の追加が予定されている ○ コンテキストパラメータ ○ when内のガード条件 ○ インライン関数の中でのcontinue, breakをサポート ○ 文字列テンプレートにおける $ のエスケープ

Slide 27

Slide 27 text

Kotlin Multiplatform (KMP) ● Kotlinコードが、複数のプラットフォーム向けの実行コードになる ● 理想: 1つのKotlinコードが、どこでも動く ● 現実 ○ ロジックなど、プラットフォームに依存しないコードは、各プラットフォームで共有可能 ○ UIなど、プラットフォームに依存するコードは、プラットフォームごとに記述する ● expect / actual

Slide 28

Slide 28 text

Kotlin Multiplatform (KMP) expect fun getName(): String fun greet(): String = "Hello, ${getName()}!" actual fun getName(): String = "JVM" fun main() { println(greet()) } actual fun getName(): String = "Android" class MainActivity: Activity() { override fun onCreate(state: Bundle?) { super.onCreate(state) Log.i("Greet", greet()) } } 共有コード commonMain JVM固有コード jvmMain Android固有コード androidMain

Slide 29

Slide 29 text

Compose Multiplatform (CMP) ● Jetpack Composeベースの宣言型UI構築フレームワーク ● 対応プラットフォーム ○ Android ○ デスクトップ(Windows, MacOS, Linux -> JVM) ○ iOS(ベータ版) ○ Wasm(アルファ版)

Slide 30

Slide 30 text

Kotlinに関係する最新ツール ● Fleet ○ JetBrains製 ○ シンプルかつパワフルなコードエディタ ○ https://www.jetbrains.com/ja-jp/fleet/ ここからダウンロード ● Amper ○ JetBrains製 ビルドツール ○ 設定をYAMLで記述する

Slide 31

Slide 31 text

KMPを始めるための環境構築 ● Android StudioやXcodeなど必要 ● セットアップを手伝ってくれるツール: KDoctor

Slide 32

Slide 32 text

KotlinによるAndroid, iOSアプリ開発の始め方 Kotlin Multiplatform Wizard (https://kmp.jetbrains.com/) でプロジェクト雛形をダウンロード

Slide 33

Slide 33 text

プラットフォームごとにソースファイルが分かれている

Slide 34

Slide 34 text

commonMain/kotlin/App.kt @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") } } } } }

Slide 35

Slide 35 text

commonMain/kotlin/App.kt @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") } } } } }

Slide 36

Slide 36 text

commonMain/kotlin/App.kt @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") } } } } }

Slide 37

Slide 37 text

commonMain/kotlin/App.kt @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") } } } } }

Slide 38

Slide 38 text

commonMain/kotlin/App.kt @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") } } } } }

Slide 39

Slide 39 text

commonMain/kotlin/App.kt @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") } } } } }

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

androidMain/kotlin/…/MainActivity.kt class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { App() } } }

Slide 42

Slide 42 text

iosMain/kotlin/MainViewController.kt fun MainViewController() = ComposeUIViewController { App() } 上記コードは、Composeとの繋ぎ込みに過ぎず、iOSアプリそのものはSwiftで記述されている // /iosApp/iosApp/ContentView.swift 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) } }

Slide 43

Slide 43 text

Fleetで実行してみる

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

共通コード class Greeting { private val platform = getPlatform() fun greet(): String { return "Hello, ${platform.name}!" } } interface Platform { val name: String } expect fun getPlatform(): Platform commonMain/kotlin/Greeting.kt commonMain/kotlin/Platform.kt

Slide 46

Slide 46 text

プラットフォーム固有のコード class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" } actual fun getPlatform(): Platform = AndroidPlatform() androidMain/kotlin/Platform.andoird.kt class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } actual fun getPlatform(): Platform = IOSPlatform() iosMain/kotlin/Platform.ios.kt

Slide 47

Slide 47 text

まとめ ● Android開発は今やKotlinで記述する利点が大きい ● その恩恵を特に受けているのが Jetpack Compose ● KMPによって、KotlinコードだけでAndroid, iOS, サーバーサイド, その他で 走るアプリを作れるし、コードの共有・分離が適切に行える ● CMPによって、Android, iOS向けのUIを同時に構築できる ご清聴ありがとうございました 長澤 太郎 @ngsw_taro