Share
Embed
TypeScript エンジニアが Android 開発の世界に飛び込んだ話
Webフロントエンド開発とAndroidネイティブ開発は一見まったく異なる世界に思えますが、TypeScriptとKotlin、Web FrontendにとAndroid Appには驚くほど多くの共通点があります。本セッションでは、バックエンドおよびフロントエンド開発を15年経験し複数の言語環境を経験してきたエンジニアが、Androidアプリ開発に挑戦した経験を共有します。複数の言語パラダイムを渡り歩いてきた視点から、両言語の類似パターン、設計思想の共通点、そして相違点から学んだ貴重な教訓を紹介し、Web Frontend領域のエンジニアにAndroidアプリ開発の敷居を下げられるようなお話をします。
https://2025.tskaigi.org/talks/yui_tang
Yui Sakamoto
May 23, 2025
Transcript
54 slides
-
アジェンダ 1. はじめに(自己紹介と背景) 2. Android × Web:UI 設計の歴史比較 3. 言語の比較:Kotlin
と TypeScript 4. UI 構築:Jetpack Compose と React の共通点と違い 5. アーキテクチャ設計:MVVM 文化とそのギャップ 6. 実践でのつまずきと気づき 7. Web エンジニアが貢献できることと未来展望 8. まとめとクロージング 2 -
自己紹介 東京生まれ 新卒で営業職 → 独学でエンジニアへ転身 メルカリでソフトウェアエンジニア・技術ブランディング等 → 2024 年 newmo
入社 猫・メタル・筋トレ・VTuber が好き 3 -
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 -
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 -
共通課題:状態と UI の整合性 Android も Web も「状態と UI の同期」 「関心の分離」で苦しんできた
Android:ライフサイクルと画面構成の複雑さ Web:状態と DOM の手動同期、グローバル変数地獄 これらの課題から、MVVM(Android)と Hooks/Context(Web)が登場した 2. Android × Web:UI 設計の歴史比較 8 -
Jetpack Compose と React が似ている理由 どちらも宣言的 UI + 関数ベース構成 状態
= UI の再現を最小構成で可能にする ただし、Compose はMVVM との併用が標準化されている点で文化的に異なる この違いが、開発者体験や責務設計における「ギャップ」になる 2. Android × Web:UI 設計の歴史比較 9 -
比較する観点 1. 型システムと null 安全 2. データ構造定義(type/interface/data class) 3. 非同期処理(Promise
vs Coroutine) 4. パターンマッチ(Discriminated Union vs sealed class) 3. 言語の比較:Kotlin と TypeScript 11 -
1. 型システムと null 安全 類似点 静的型付き言語(型推論、ユニオン型) コンパイル時の型チェックによる品質担保 相違点 TypeScript: string
| null | undefined として union 型で表現 Kotlin:nullable 型は String? として明示的に区別され、厳格に扱われる 3. 言語の比較:Kotlin と TypeScript 12 -
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 -
2. データ構造定義:type / interface / data class 類似点 データ構造を型定義し、安全に受け渡し イミュータブル設計が基本
相違点 TypeScript は構造的型付け(プロパティが一致すれば OK) Kotlin は名義的型付け(定義された型の一致が必要) 3. 言語の比較:Kotlin と TypeScript 14 -
データ構造:コード比較 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 -
3. 非同期処理:Promise と Coroutine 類似点 処理の非同期化による UI スレッドのブロック回避 async/await 構文により直感的に記述可能
相違点 TypeScript:Promise ベース、スレッド制御は明示されない Kotlin:Coroutine ベース、スレッド切り替えとキャンセル制御が可能 3. 言語の比較:Kotlin と TypeScript 16 -
非同期処理:コード比較 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 -
4. パターンマッチと制御構文 類似点 条件分岐と型による処理分離 安全な網羅性チェック(TypeScript は switch、Kotlin は when) 相違点
TypeScript:Discriminated Union + switch 文 Kotlin:sealed class + when 式(else 不要で網羅性担保) 3. 言語の比較:Kotlin と TypeScript 18 -
分岐のコード比較 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 -
分岐のコード比較 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 -
比較する観点 1. UI の記述構文とデータバインディング 2. 状態管理のモデル(useState vs remember) 3. コンポジションと再描画の仕組み
4. 開発支援機能(Storybook と Preview) 4. UI 構築:Jetpack Compose と React 22 -
1. UI の記述構文とデータバインディング 類似点 JSX と Compose DSL はどちらも「関数で UI
を構築」 宣言的記述スタイル、再利用性が高い 相違点 JSX は HTML ライクな構文、DSL は Kotlin そのもの Compose は「式ベース」、React は「JSX ツリー」 4. UI 構築:Jetpack Compose と React 23 -
1. UI の記述構文とデータバインディング // tsx function Greeting({ name }: {
name: string }) { return <Text>Hello, {name}</Text>; } // compose @Composable fun Greeting(name: String) { Text("Hello, $name") } 4. UI 構築:Jetpack Compose と React 24 -
2. 状態管理のモデル 類似点 UI 状態をローカルに保持、変化をトリガに再描画 宣言的に定義される「ローカルな状態の粒度」が基本 相違点 React は useState
Compose は remember + mutableStateOf 4. UI 構築:Jetpack Compose と React 25 -
2. 状態管理のモデル // tsx const [count, setCount] = useState(0); //
compose var count by remember { mutableStateOf(0) } 4. UI 構築:Jetpack Compose と React 26 -
状態とイベントの統合的構成 // tsx <button onClick={() => setCount(count + 1)}>Click</button> //
compose Button(onClick = { count++ }) { Text("Click") } React は Props + 関数、Compose はラムダによる記述 4. UI 構築:Jetpack Compose と React 27 -
3. コンポジションと再描画 類似点 状態の変化に応じて UI を差分再描画 Component / Composable 単位で再構成される
相違点 React:Virtual DOM による差分検出 Compose:Recomposition による実行時最適化 Compose は状態追跡により必要最小限の再評価を行う 参考:Compose Recomposition Guide 4. UI 構築:Jetpack Compose と React 28 -
4. 開発支援機能:Preview vs Storybook Compose の Preview @Preview(showBackground = true)
@Composable fun PreviewGreeting() { Greeting("Android") } Theme や画面サイズごとの確認も可能 Android Studio と統合、実機プレビューに強い 4. UI 構築:Jetpack Compose と React 29 -
4. 開発支援機能:Preview vs Storybook React の Storybook export const Primary
= () => <Greeting name="React" />; コンポーネント単位のバリエーション管理に最適 開発中のスタイル確認や UI ライブラリ化に有効 4. UI 構築:Jetpack Compose と React 30 -
宣言的 UI の収束 項目 React Jetpack Compose 記述スタイル JSX DSL(Kotlin)
再描画方式 Virtual DOM Recomposition 状態管理 useState, Context remember, ViewModel 開発支援 Storybook @Preview(Studio 統合) 両者は目的が近づきつつも、設計文化とプラットフォーム特性により差異が存在する 4. UI 構築:Jetpack Compose と React 31 -
なぜ ViewModel が重要なのか? Android の UI はライフサイクルの影響を強く受ける 状態とロジックを UI から切り離し、再利用・テスト性を高めるために
ViewModel を導入 Jetpack の公式アーキテクチャでは ViewModel 中心の MVVM を推奨 参考:Guide to app architecture - Android Developers 5. アーキテクチャ設計:MVVM 文化とそのギャップ 33 -
ViewModel の構成例 class MainViewModel : ViewModel() { private val _uiState
= MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState fun onEvent(event: UiEvent) { // 状態更新とビジネスロジック } } 状態を StateFlow で保持し、Composable 側で collectAsState() イベント処理は ViewModel で集約 5. アーキテクチャ設計:MVVM 文化とそのギャップ 35 -
React との責務設計の違い 項目 Compose + ViewModel React + Hooks 状態保持
ViewModel useState / useReducer UI イベント処理 ViewModel 側で記述 関数内で直接記述 状態反映 Flow → Composable 値変化で再レンダリング データ流れ 外部 → 内部(UDF) 親 → 子 + 状態リフトアップ ViewModel による一方向データフローが標準文化になっている 5. アーキテクチャ設計:MVVM 文化とそのギャップ 36 -
なぜ React のようにできないのか? Android は Activity/Fragment のライフサイクルが複雑 ViewModel による状態のサバイバルが設計上求められる UI/状態/副作用の責務分離が「前提」になっている
React では UI と状態の局所管理が柔軟に許容されているが、Android では明示的な管理が文 化として定着 5. アーキテクチャ設計:MVVM 文化とそのギャップ 37 -
ViewModel を使わない構成は可能か? @Composable fun Counter() { var count by rememberSaveable
{ mutableStateOf(0) } Button(onClick = { count++ }) { Text("Count: $count") } } 小規模アプリでは ViewModel なしでも完結可能 状態の永続化や再利用には限界がある Jetpack Compose の柔軟性により、用途に応じた選択が可能 5. アーキテクチャ設計:MVVM 文化とそのギャップ 38 -
Web エンジニアが持ち込める視点 状態のスコープ設計と責務分離のバランス感覚 ViewModel の「設計しすぎ」問題への問い直し 状態 = UI という自然な発想 Web
の知見は、Compose の今後の進化において必ず活きる 5. アーキテクチャ設計:MVVM 文化とそのギャップ 40 -
Gradle まわりの初見殺し build.gradle.kts の記述が抽象的で読みにくい 設定ファイルが多層構造で、何がどこに効いているか追いにくい Vite や tsconfig の感覚と異なり、「Gradle の言語」に慣れる必要がある
解決のヒント:公式テンプレートと `dependencies` の構成から読むとわかりやすい 6. 実践でのつまずきと気づき 42 -
Coroutine と非同期の違い suspend 関数は UI スレッドで呼べないことがある CoroutineScope , Dispatcher ,
withContext など構成要素が多い launch {} と async {} の使い分け、キャンセル設計に戸惑う viewModelScope.launch { val result = withContext(Dispatchers.IO) { repository.fetch() } _state.value = result } async/await の延長では捉えきれない「並行性とスレッド」の設計が求められる 6. 実践でのつまずきと気づき 43 -
Compose Preview の落とし穴 @Preview が効かないことがある(条件:Composable の構造や Theme) 再ビルド地獄:Preview 表示に必要な Rebuild
が多く発生 Preview で完結させるには UI 単位の分割とテーマ切り替えの工夫が必要 @Preview @Composable fun ButtonPreview() { MyTheme { MyButton(onClick = {}) { Text("Click me") } } } Storybook 的な使い方をするには設計の工夫が必要 6. 実践でのつまずきと気づき 44 -
状態の保持と再描画のズレ remember で状態を保持するも、意図しない Recomposition が発生することがある LaunchedEffect , SideEffect , derivedStateOf
など副作用との付き合いが必要 状態が「意図せず更新される」ことに違和感を覚える var name by remember { mutableStateOf("yui") } LaunchedEffect(Unit) { println("This runs once") } React の useEffect に似ているが、「再コンポジション = 再評価」の前提が強い 6. 実践でのつまずきと気づき 45 -
気づき:構造の自由度が高いからこそ「設計」が問われる React では eslint + hooks rule によりある程度制約されていた Compose は
ViewModel 有無・状態の粒度・UI/非 UI の分離まで開発者次第 「書けてしまう」ことで責務設計と状態の一貫性維持が難しくなる 設計力と責任が問われる世界。それゆえに Web エンジニアの経験が活きる場面も多い 6. 実践でのつまずきと気づき 46 -
宣言的 UI 経験の移植可能性 React / Vue / SolidJS などで培った宣言的構築の経験は Compose
に直結する 状態と UI の一貫性、再描画の制御感覚は共通 「状態の粒度設計」 「責務の分離と再利用」への感度が高い Compose での状態設計・再利用パターンで非常に重要 設計フェーズで活きる視点 Storybook 的 UI 分割・Preview 活用の発想 Props/State vs ViewModel/StateFlow の違いに対する抽象化能力 "Composable な責務分離"を前提にした開発スタイルの提案 Jetpack Compose よりも以前からUI実装をしていた皆さんの外部視点が活きる場面は多い 7. Web エンジニアが貢献できることと未来展望 48 -
Kotlin Multiplatform(KMP)という未来 Kotlin Multiplatform Mobile による ViewModel やビジネスロジックの共有 JetBrains による
Compose for Web / Desktop の進化 Web / Android を跨いだ UI 戦略が現実的に見えてきた expect class PlatformViewModel() { val state: StateFlow<UIState> } モバイル開発と Web 開発が「Kotlin」という言語で交わる可能性が広がっている 参考:[Kotlin Multiplatform Overview](https://kotlinlang.org/lp/multiplatform/) 7. Web エンジニアが貢献できることと未来展望 49 -
境界を越えるエンジニアの役割 Web / モバイル / フロントエンド / インフラの境界が曖昧になっている 経験を横展開する「越境型エンジニア」が価値を持つ時代 Web
エンジニアが Kotlin・Compose に取り組む意義はそこにある 7. Web エンジニアが貢献できることと未来展望 50 -
このトークで伝えたかったこと Kotlin と TypeScript は文法だけでなく思想にも通じる部分が多い Jetpack Compose は React に近いが、設計文化が異なるという発見
MVVM を中心とした Android の現在地は、「歴史の帰結」でもある 異なる世界に"親しさ"を見出すことは、学びと越境の第一歩になる これから始める人に伝えたいこと Compose の世界は柔軟で、Web エンジニアにも理解しやすい構造を持つ ただし、設計や状態の責任は "開発者に委ねられている" それゆえに、Web の経験が本当に活きる場面が多い 8. まとめとクロージング 52