Slide 1

Slide 1 text

TypeScript エンジニアが Android 開発の世界に飛び込んだ話 yui_tang (Yui Sakamoto) newmo 株式会社 / ソフトウェアエンジニア

Slide 2

Slide 2 text

アジェンダ 1. はじめに(自己紹介と背景) 2. Android × Web:UI 設計の歴史比較 3. 言語の比較:Kotlin と TypeScript 4. UI 構築:Jetpack Compose と React の共通点と違い 5. アーキテクチャ設計:MVVM 文化とそのギャップ 6. 実践でのつまずきと気づき 7. Web エンジニアが貢献できることと未来展望 8. まとめとクロージング 2

Slide 3

Slide 3 text

自己紹介 東京生まれ 新卒で営業職 → 独学でエンジニアへ転身 メルカリでソフトウェアエンジニア・技術ブランディング等 → 2024 年 newmo 入社 猫・メタル・筋トレ・VTuber が好き 3

Slide 4

Slide 4 text

2. Android × Web:UI 設計の歴史比較 4

Slide 5

Slide 5 text

なぜ歴史を振り返るのか? 技術選定や設計思想は、その時代の制約と課題から生まれる Android 開発では「なぜ今 MVVM なのか?」を理解するには、変遷を知ることが不可欠 2. Android × Web:UI 設計の歴史比較 5

Slide 6

Slide 6 text

Android UI の進化 年代 UI 記述 構造管理 アーキテクチャ 2008 年 XML + Java Activity ほぼ MVC 的 2013 年 XML + Java Fragment(画面の分割) 無理やり責務分離 2017 年 XML + Kotlin ViewModel + LiveData MVVM(公式に推奨) 2020 年〜 Jetpack Compose ViewModel または Composable 単体 柔軟(MVVM, MVI な ど) 参考:Android UI History, Jetpack Architecture Guide 2. Android × Web:UI 設計の歴史比較 6

Slide 7

Slide 7 text

Web UI の進化(ざっくり) 年代 主な技術 特徴 2006 jQuery DOM 操作中心、非構造的 2012 Backbone/AngularJS MVC/MVVM ライクな構成 2015 React 登場 関数型 UI、状態の局所化 2019 Hooks 関数と状態の統合的な構成 参考:A Brief History of React, MDN - Web Frameworks 2. Android × Web:UI 設計の歴史比較 7

Slide 8

Slide 8 text

共通課題:状態と UI の整合性 Android も Web も「状態と UI の同期」 「関心の分離」で苦しんできた Android:ライフサイクルと画面構成の複雑さ Web:状態と DOM の手動同期、グローバル変数地獄 これらの課題から、MVVM(Android)と Hooks/Context(Web)が登場した 2. Android × Web:UI 設計の歴史比較 8

Slide 9

Slide 9 text

Jetpack Compose と React が似ている理由 どちらも宣言的 UI + 関数ベース構成 状態 = UI の再現を最小構成で可能にする ただし、Compose はMVVM との併用が標準化されている点で文化的に異なる この違いが、開発者体験や責務設計における「ギャップ」になる 2. Android × Web:UI 設計の歴史比較 9

Slide 10

Slide 10 text

3. 言語の比較:Kotlin と TypeScript 10

Slide 11

Slide 11 text

比較する観点 1. 型システムと null 安全 2. データ構造定義(type/interface/data class) 3. 非同期処理(Promise vs Coroutine) 4. パターンマッチ(Discriminated Union vs sealed class) 3. 言語の比較:Kotlin と TypeScript 11

Slide 12

Slide 12 text

1. 型システムと null 安全 類似点 静的型付き言語(型推論、ユニオン型) コンパイル時の型チェックによる品質担保 相違点 TypeScript: string | null | undefined として union 型で表現 Kotlin:nullable 型は String? として明示的に区別され、厳格に扱われる 3. 言語の比較:Kotlin と TypeScript 12

Slide 13

Slide 13 text

null 安全:コード比較 function greet(name?: string) { return `Hello, ${name ?? "Guest"}`; } fun greet(name: String?) = "Hello, ${name ?: "Guest"}" Kotlin は null 安全性が言語仕様として明文化されている。 参考:Kotlin Docs - Null Safety 3. 言語の比較:Kotlin と TypeScript 13

Slide 14

Slide 14 text

2. データ構造定義:type / interface / data class 類似点 データ構造を型定義し、安全に受け渡し イミュータブル設計が基本 相違点 TypeScript は構造的型付け(プロパティが一致すれば OK) Kotlin は名義的型付け(定義された型の一致が必要) 3. 言語の比較:Kotlin と TypeScript 14

Slide 15

Slide 15 text

データ構造:コード比較 type User = { id: string; name: string; }; data class User(val id: String, val name: String) Kotlin は`equals`や`hashCode`も自動生成され、値オブジェクトとしての特性が強い 参考:Kotlin Docs - Data Classes 3. 言語の比較:Kotlin と TypeScript 15

Slide 16

Slide 16 text

3. 非同期処理:Promise と Coroutine 類似点 処理の非同期化による UI スレッドのブロック回避 async/await 構文により直感的に記述可能 相違点 TypeScript:Promise ベース、スレッド制御は明示されない Kotlin:Coroutine ベース、スレッド切り替えとキャンセル制御が可能 3. 言語の比較:Kotlin と TypeScript 16

Slide 17

Slide 17 text

非同期処理:コード比較 async function fetchData() { const res = await fetch(url); return await res.json(); } suspend fun fetchData(): Data = withContext(Dispatchers.IO) { api.get(url) } Kotlin は非同期処理において Dispatcher などのコンテキスト指定ができる 参考:Kotlin Coroutines Guide 3. 言語の比較:Kotlin と TypeScript 17

Slide 18

Slide 18 text

4. パターンマッチと制御構文 類似点 条件分岐と型による処理分離 安全な網羅性チェック(TypeScript は switch、Kotlin は when) 相違点 TypeScript:Discriminated Union + switch 文 Kotlin:sealed class + when 式(else 不要で網羅性担保) 3. 言語の比較:Kotlin と TypeScript 18

Slide 19

Slide 19 text

分岐のコード比較 TypeScript type State = { type: "loading" } | { type: "success"; data: string }; function render(s: State) { switch (s.type) { case "loading": return "Loading..."; case "success": return s.data; } } 3. 言語の比較:Kotlin と TypeScript 19

Slide 20

Slide 20 text

分岐のコード比較 Kotlin sealed class State object Loading : State() data class Success(val data: String) : State() fun render(s: State): String = when(s) { is Loading -> "Loading..." is Success -> s.data } sealed class の活用で型安全かつ網羅的な分岐が可能 参考:Kotlin Docs - Sealed Classes 3. 言語の比較:Kotlin と TypeScript 20

Slide 21

Slide 21 text

4. UI 構築:Jetpack Compose と React の共通点と違い 21

Slide 22

Slide 22 text

比較する観点 1. UI の記述構文とデータバインディング 2. 状態管理のモデル(useState vs remember) 3. コンポジションと再描画の仕組み 4. 開発支援機能(Storybook と Preview) 4. UI 構築:Jetpack Compose と React 22

Slide 23

Slide 23 text

1. UI の記述構文とデータバインディング 類似点 JSX と Compose DSL はどちらも「関数で UI を構築」 宣言的記述スタイル、再利用性が高い 相違点 JSX は HTML ライクな構文、DSL は Kotlin そのもの Compose は「式ベース」、React は「JSX ツリー」 4. UI 構築:Jetpack Compose と React 23

Slide 24

Slide 24 text

1. UI の記述構文とデータバインディング // tsx function Greeting({ name }: { name: string }) { return Hello, {name}; } // compose @Composable fun Greeting(name: String) { Text("Hello, $name") } 4. UI 構築:Jetpack Compose と React 24

Slide 25

Slide 25 text

2. 状態管理のモデル 類似点 UI 状態をローカルに保持、変化をトリガに再描画 宣言的に定義される「ローカルな状態の粒度」が基本 相違点 React は useState Compose は remember + mutableStateOf 4. UI 構築:Jetpack Compose と React 25

Slide 26

Slide 26 text

2. 状態管理のモデル // tsx const [count, setCount] = useState(0); // compose var count by remember { mutableStateOf(0) } 4. UI 構築:Jetpack Compose と React 26

Slide 27

Slide 27 text

状態とイベントの統合的構成 // tsx setCount(count + 1)}>Click // compose Button(onClick = { count++ }) { Text("Click") } React は Props + 関数、Compose はラムダによる記述 4. UI 構築:Jetpack Compose と React 27

Slide 28

Slide 28 text

3. コンポジションと再描画 類似点 状態の変化に応じて UI を差分再描画 Component / Composable 単位で再構成される 相違点 React:Virtual DOM による差分検出 Compose:Recomposition による実行時最適化 Compose は状態追跡により必要最小限の再評価を行う 参考:Compose Recomposition Guide 4. UI 構築:Jetpack Compose と React 28

Slide 29

Slide 29 text

4. 開発支援機能:Preview vs Storybook Compose の Preview @Preview(showBackground = true) @Composable fun PreviewGreeting() { Greeting("Android") } Theme や画面サイズごとの確認も可能 Android Studio と統合、実機プレビューに強い 4. UI 構築:Jetpack Compose と React 29

Slide 30

Slide 30 text

4. 開発支援機能:Preview vs Storybook React の Storybook export const Primary = () => ; コンポーネント単位のバリエーション管理に最適 開発中のスタイル確認や UI ライブラリ化に有効 4. UI 構築:Jetpack Compose と React 30

Slide 31

Slide 31 text

宣言的 UI の収束 項目 React Jetpack Compose 記述スタイル JSX DSL(Kotlin) 再描画方式 Virtual DOM Recomposition 状態管理 useState, Context remember, ViewModel 開発支援 Storybook @Preview(Studio 統合) 両者は目的が近づきつつも、設計文化とプラットフォーム特性により差異が存在する 4. UI 構築:Jetpack Compose と React 31

Slide 32

Slide 32 text

5. アーキテクチャ設計:MVVM 文化とそのギャ ップ 32

Slide 33

Slide 33 text

なぜ ViewModel が重要なのか? Android の UI はライフサイクルの影響を強く受ける 状態とロジックを UI から切り離し、再利用・テスト性を高めるために ViewModel を導入 Jetpack の公式アーキテクチャでは ViewModel 中心の MVVM を推奨 参考:Guide to app architecture - Android Developers 5. アーキテクチャ設計:MVVM 文化とそのギャップ 33

Slide 34

Slide 34 text

「Google 公式の Example が MVVM 前提だから変わらないんですよ」 5. アーキテクチャ設計:MVVM 文化とそのギャップ 34

Slide 35

Slide 35 text

ViewModel の構成例 class MainViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _uiState fun onEvent(event: UiEvent) { // 状態更新とビジネスロジック } } 状態を StateFlow で保持し、Composable 側で collectAsState() イベント処理は ViewModel で集約 5. アーキテクチャ設計:MVVM 文化とそのギャップ 35

Slide 36

Slide 36 text

React との責務設計の違い 項目 Compose + ViewModel React + Hooks 状態保持 ViewModel useState / useReducer UI イベント処理 ViewModel 側で記述 関数内で直接記述 状態反映 Flow → Composable 値変化で再レンダリング データ流れ 外部 → 内部(UDF) 親 → 子 + 状態リフトアップ ViewModel による一方向データフローが標準文化になっている 5. アーキテクチャ設計:MVVM 文化とそのギャップ 36

Slide 37

Slide 37 text

なぜ React のようにできないのか? Android は Activity/Fragment のライフサイクルが複雑 ViewModel による状態のサバイバルが設計上求められる UI/状態/副作用の責務分離が「前提」になっている React では UI と状態の局所管理が柔軟に許容されているが、Android では明示的な管理が文 化として定着 5. アーキテクチャ設計:MVVM 文化とそのギャップ 37

Slide 38

Slide 38 text

ViewModel を使わない構成は可能か? @Composable fun Counter() { var count by rememberSaveable { mutableStateOf(0) } Button(onClick = { count++ }) { Text("Count: $count") } } 小規模アプリでは ViewModel なしでも完結可能 状態の永続化や再利用には限界がある Jetpack Compose の柔軟性により、用途に応じた選択が可能 5. アーキテクチャ設計:MVVM 文化とそのギャップ 38

Slide 39

Slide 39 text

責務分離と選択可能性 ViewModel:UI とロジックの分離、ライフサイクル耐性、テスト容易性 Composable 内状態管理:軽量・局所完結・柔軟な UI 設計 正解はない。アプリケーションの規模・構造・チーム構成によって最適な設計は変わる 5. アーキテクチャ設計:MVVM 文化とそのギャップ 39

Slide 40

Slide 40 text

Web エンジニアが持ち込める視点 状態のスコープ設計と責務分離のバランス感覚 ViewModel の「設計しすぎ」問題への問い直し 状態 = UI という自然な発想 Web の知見は、Compose の今後の進化において必ず活きる 5. アーキテクチャ設計:MVVM 文化とそのギャップ 40

Slide 41

Slide 41 text

6. 実践でのつまずきと気づき 41

Slide 42

Slide 42 text

Gradle まわりの初見殺し build.gradle.kts の記述が抽象的で読みにくい 設定ファイルが多層構造で、何がどこに効いているか追いにくい Vite や tsconfig の感覚と異なり、「Gradle の言語」に慣れる必要がある 解決のヒント:公式テンプレートと `dependencies` の構成から読むとわかりやすい 6. 実践でのつまずきと気づき 42

Slide 43

Slide 43 text

Coroutine と非同期の違い suspend 関数は UI スレッドで呼べないことがある CoroutineScope , Dispatcher , withContext など構成要素が多い launch {} と async {} の使い分け、キャンセル設計に戸惑う viewModelScope.launch { val result = withContext(Dispatchers.IO) { repository.fetch() } _state.value = result } async/await の延長では捉えきれない「並行性とスレッド」の設計が求められる 6. 実践でのつまずきと気づき 43

Slide 44

Slide 44 text

Compose Preview の落とし穴 @Preview が効かないことがある(条件:Composable の構造や Theme) 再ビルド地獄:Preview 表示に必要な Rebuild が多く発生 Preview で完結させるには UI 単位の分割とテーマ切り替えの工夫が必要 @Preview @Composable fun ButtonPreview() { MyTheme { MyButton(onClick = {}) { Text("Click me") } } } Storybook 的な使い方をするには設計の工夫が必要 6. 実践でのつまずきと気づき 44

Slide 45

Slide 45 text

状態の保持と再描画のズレ remember で状態を保持するも、意図しない Recomposition が発生することがある LaunchedEffect , SideEffect , derivedStateOf など副作用との付き合いが必要 状態が「意図せず更新される」ことに違和感を覚える var name by remember { mutableStateOf("yui") } LaunchedEffect(Unit) { println("This runs once") } React の useEffect に似ているが、「再コンポジション = 再評価」の前提が強い 6. 実践でのつまずきと気づき 45

Slide 46

Slide 46 text

気づき:構造の自由度が高いからこそ「設計」が問われる React では eslint + hooks rule によりある程度制約されていた Compose は ViewModel 有無・状態の粒度・UI/非 UI の分離まで開発者次第 「書けてしまう」ことで責務設計と状態の一貫性維持が難しくなる 設計力と責任が問われる世界。それゆえに Web エンジニアの経験が活きる場面も多い 6. 実践でのつまずきと気づき 46

Slide 47

Slide 47 text

7. Web エンジニアが貢献できること と未来展望 47

Slide 48

Slide 48 text

宣言的 UI 経験の移植可能性 React / Vue / SolidJS などで培った宣言的構築の経験は Compose に直結する 状態と UI の一貫性、再描画の制御感覚は共通 「状態の粒度設計」 「責務の分離と再利用」への感度が高い Compose での状態設計・再利用パターンで非常に重要 設計フェーズで活きる視点 Storybook 的 UI 分割・Preview 活用の発想 Props/State vs ViewModel/StateFlow の違いに対する抽象化能力 "Composable な責務分離"を前提にした開発スタイルの提案 Jetpack Compose よりも以前からUI実装をしていた皆さんの外部視点が活きる場面は多い 7. Web エンジニアが貢献できることと未来展望 48

Slide 49

Slide 49 text

Kotlin Multiplatform(KMP)という未来 Kotlin Multiplatform Mobile による ViewModel やビジネスロジックの共有 JetBrains による Compose for Web / Desktop の進化 Web / Android を跨いだ UI 戦略が現実的に見えてきた expect class PlatformViewModel() { val state: StateFlow } モバイル開発と Web 開発が「Kotlin」という言語で交わる可能性が広がっている 参考:[Kotlin Multiplatform Overview](https://kotlinlang.org/lp/multiplatform/) 7. Web エンジニアが貢献できることと未来展望 49

Slide 50

Slide 50 text

境界を越えるエンジニアの役割 Web / モバイル / フロントエンド / インフラの境界が曖昧になっている 経験を横展開する「越境型エンジニア」が価値を持つ時代 Web エンジニアが Kotlin・Compose に取り組む意義はそこにある 7. Web エンジニアが貢献できることと未来展望 50

Slide 51

Slide 51 text

8. まとめとクロージング 51

Slide 52

Slide 52 text

このトークで伝えたかったこと Kotlin と TypeScript は文法だけでなく思想にも通じる部分が多い Jetpack Compose は React に近いが、設計文化が異なるという発見 MVVM を中心とした Android の現在地は、「歴史の帰結」でもある 異なる世界に"親しさ"を見出すことは、学びと越境の第一歩になる これから始める人に伝えたいこと Compose の世界は柔軟で、Web エンジニアにも理解しやすい構造を持つ ただし、設計や状態の責任は "開発者に委ねられている" それゆえに、Web の経験が本当に活きる場面が多い 8. まとめとクロージング 52

Slide 53

Slide 53 text

越境を恐れず、共通点から学ぼう 技術的な越境は、異文化理解と同じ "なんとなく似ている" から入って、"本質的な違い"にたどり着ける そのプロセス自体が、エンジニアとしての成長に繋がる 8. まとめとクロージング 53

Slide 54

Slide 54 text

ご清聴ありがとうございました! 54