Kotlin: KotlinによるAndroid開発の基礎
最終更新:2026-08-26
Android開発ではKotlinが全面的に採用されています。Jetpack ComposeはKotlin DSLを用いて宣言型のUIを構築し、ViewModelとStateFlowがリアクティブな状態を管理し、Roomとコルーチンがデータ層を簡素化します。CharlieはAndroid用の注文ダッシュボードを構築しています。
1. 学習内容
- アクティビティ/フラグメントのライフサイクルとKotlinとの統合
- Jetpack Compose:宣言型UI + Kotlin DSL
- ViewModel + StateFlow:リアクティブな状態管理
- ルームデータベース:エンティティ + DAO + コルーチン
- Charlieの実践ガイド:Android注文ダッシュボード
2. ある開発者の実体験
(1) 課題:命令型UIにおける状態同期の悪夢
AliceはXMLとRecyclerViewを使って注文リストを作成しました。状態が変化するたびに、手動でUIを更新する必要がありました。データが変更されたらnotifyDataSetChanged()を呼び出し、読み込み中はプログレスバーを表示・非表示にする――10の状態に対応するために、20行以上のUI同期コードが必要でした。
(2) 宣言型UIソリューションの構築
KOTLIN
// XML + RecyclerView: imperative, error-prone
// recyclerView.adapter.notifyDataSetChanged()
// Compose: declarative, automatic
@Composable
fun OrderList(orders: List<Order>) {
LazyColumn {
items(orders) { order ->
OrderCard(order) // UI automatically reflects data changes
}
}
}
Composeは宣言型であり、UIは状態の関数として定義されます。データが変更されると、手動での同期作業を必要とせずにUIが自動的に更新されます。
3. アクティビティのライフサイクル
(1) ライフサイクル・コールバック
KOTLIN
class OrderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
OrderScreen()
}
}
override fun onResume() {
super.onResume()
// Refresh data when activity comes to foreground
}
override fun onPause() {
super.onPause()
// Save draft data
}
}
(2) ライフサイクル状態図
stateDiagram-v2
[*] --> Created: onCreate()
Created --> Started: onStart()
Started --> Resumed: onResume()
Resumed --> Started: onPause()
Started --> Created: onStop()
Created --> [*]: onDestroy()
Started --> Resumed: onResume()
(3) ライフサイクル・コールバックの比較
| コールバック | トリガー | 一般的な動作 |
|---|---|---|
onCreate(bundle) |
アクティビティが作成されました | UI、ViewModelの初期化 |
onStart() |
表示されるが操作はできない | リスナーを登録 |
onResume() |
フォーカスを取得 | アニメーションを開始、データを更新 |
onPause() |
フォーカスが外れる | アニメーションを一時停止し、下書きを保存 |
onStop() |
表示されなくなりました | リソースを解放 |
onDestroy() |
破壊中 | リソースのクリーンアップ |
4. Jetpack Compose
(1) 組み合わせ可能な関数
KOTLIN
@Composable
fun OrderCard(order: Order) {
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = order.id, style = MaterialTheme.typography.titleMedium)
Text(text = order.customer, style = MaterialTheme.typography.bodyMedium)
}
Text(
text = "\$${order.total} USD",
style = MaterialTheme.typography.titleLarge
)
}
}
}
(2) リストと状態
KOTLIN
@Composable
fun OrderListScreen(viewModel: OrderViewModel = viewModel()) {
val orders by viewModel.orders.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
if (isLoading) {
CircularProgressIndicator()
} else {
LazyColumn {
items(orders) { order ->
OrderCard(order)
}
}
}
}
(3) レンダリングパイプラインの構築
flowchart TD
A[State Change] --> B[Composition<br/>Run @Composable functions]
B --> C[Layout<br/>Measure and position]
C --> D[Drawing<br/>Render to canvas]
D --> E[UI Updated]
(4) XML と Compose の比較
| ディメンション | XML + ビュー | Jetpack Compose |
|---|---|---|
| パラダイム | 命令型 | 宣言型 |
| 言語 | XML + Kotlin | 純粋なKotlin |
| 状態の同期 | 手動 | 自動 |
| コード量 | 多い (XML + アダプター + ビューホルダー) | 少ない (@Composable) |
| プレビュー | 実行が必要 | IDE リアルタイムプレビュー |
| 習得の難易度 | 中程度 | 中程度(新しいパラダイム) |
5. ViewModel + StateFlow
(1) ViewModel
KOTLIN
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
try {
_orders.value = orderRepository.findAll()
} finally {
_isLoading.value = false
}
}
}
}
(2) StateFlow 対 LiveData
| ディメンション | LiveData | StateFlow |
|---|---|---|
| 初期値 | 不要 | 必須 |
| コルーチンのサポート | アダプタが必要 | ネイティブ |
| スレッド | メインスレッド | 任意のスレッド |
| ライフサイクル | 認識済み | 必要 repeatOnLifecycle |
| 推奨事項 | レガシーコード | 新規プロジェクト |
6. 部屋データベース
(1) エンティティ + Dao
KOTLIN
@Entity(tableName = "orders")
data class OrderEntity(
@PrimaryKey val id: String,
val total: Double,
val status: String,
val customer: String,
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface OrderDao {
@Query("SELECT * FROM orders ORDER BY createdAt DESC")
fun getAllOrders(): Flow<List<OrderEntity>>
@Query("SELECT * FROM orders WHERE id = :id")
suspend fun getById(id: String): OrderEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(order: OrderEntity)
@Delete
suspend fun delete(order: OrderEntity)
}
(2) 部屋の構成要素
| コンポーネント | アノテーション | 責任 |
|---|---|---|
| エンティティ | @Entity |
データベーステーブルのマッピング |
| Dao | @Dao |
データアクセス操作 |
| データベース | @Database |
データベースのエントリポイント |
7. 完全な例:Android注文ダッシュボード
▶ サンプル:Android注文ダッシュボード
KOTLIN
// ============================================
// OrderProcessor - Android Order Dashboard
// Feature: Compose UI + ViewModel + StateFlow
// ============================================
// --- Domain ---
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// --- ViewModel ---
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
loadOrders()
}
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
delay(500) // Simulate network
_orders.value = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "PENDING", "Bob"),
Order("ORD-003", 2_500.00, "SHIPPED", "Charlie"),
Order("ORD-004", 8_900.00, "CONFIRMED", "Bob"),
Order("ORD-005", 45.50, "CANCELLED", "Alice")
)
_isLoading.value = false
}
}
fun totalRevenue(): Double = _orders.value
.filter { it.status != "CANCELLED" }
.sumOf { it.total }
}
// --- Compose UI (Conceptual - requires Android runtime) ---
// @Composable
// fun OrderScreen(viewModel: OrderViewModel = viewModel()) {
// val orders by viewModel.orders.collectAsState()
// val isLoading by viewModel.isLoading.collectAsState()
//
// Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Text("Order Dashboard", style = MaterialTheme.typography.headlineMedium)
// Text("Revenue: \$${viewModel.totalRevenue()} USD", style = MaterialTheme.typography.titleLarge)
//
// if (isLoading) {
// CircularProgressIndicator()
// } else {
// LazyColumn {
// items(orders) { order -> OrderCard(order) }
// }
// }
// }
// }
// --- JVM Demo (simulating the ViewModel logic) ---
fun main() {
val viewModel = OrderViewModel()
// Simulate observing state
Thread.sleep(1000) // Wait for loading
println("=== Android Order Dashboard ===")
println("Revenue: \$${viewModel.totalRevenue()} USD")
println("Loading: ${viewModel.isLoading.value}")
viewModel.orders.value.forEach { order ->
val icon = when (order.status) {
"CONFIRMED" -> "✅"
"PENDING" -> "⏳"
"SHIPPED" -> "📦"
"CANCELLED" -> "❌"
else -> "?"
}
println(" $icon ${order.id}: \$${order.total} USD (${order.customer}) [${order.status}]")
}
}
出力:
TEXT
📖 参照専用
=== Android Order Dashboard ===
Revenue: $26699.99 USD
Loading: false
✅ ORD-001: $299.99 USD (Alice) [CONFIRMED]
⏳ ORD-002: $15000.0 USD (Bob) [PENDING]
📦 ORD-003: $2500.0 USD (Charlie) [SHIPPED]
✅ ORD-004: $8900.0 USD (Bob) [CONFIRMED]
❌ ORD-005: $45.5 USD (Alice) [CANCELLED]
❓ よくある質問
Q Compose を使わなければならないのでしょうか?XML を引き続き使用することはできますか?
A XML を引き続き使用することは可能ですが、Google では新規プロジェクトのデフォルトとして Compose を推奨しています。Compose と XML は共存可能です(
ComposeView を使用すると、XML 内に Compose を埋め込むことができます)。Q ViewModelとPresenterの違いは何ですか?
A ViewModelは構成の変更(画面の回転など)があっても破棄されず、データは自動的に保持されます。一方、Presenterでは、構成の変更時に手動で保存・復元を行う必要があります。
Q StateFlowとSharedFlowのどちらを選べばよいですか?
A StateFlowは状態を保持するためのものです(初期値が必要で、重複を排除します)。 SharedFlowはイベントストリーム用です(マルチキャスト、初期値なし)。UIの状態にはStateFlowを、ワンショットイベントにはSharedFlowを使用してください。
Q Roomはコルーチンに対応していますか?
A はい。Dao
suspend 関数は、Roomのスレッドプール上で自動的に実行されます。クエリ Flow は、データが変更されると自動的に再発行されます。Q Composeのパフォーマンスはどうですか?
A Composeはスマートリコンポジション(変更されたComposableのみを再描画)を採用しており、そのパフォーマンスは手書きのViewに匹敵します。 Composable内部での計算は避け、キャッシュには
remember および derivedStateOf を使用してください。Q Compose でのナビゲーションはどのように扱えばよいですか?
A
NavHost + composable() を使用してナビゲーショングラフを定義し、navController.navigate() を使用してナビゲーションを行います。Compose 1.0 以降では、型安全なナビゲーションが利用可能です。📖 まとめ
- Android開発は現在、完全にKotlinベースとなっています。Jetpack Composeが新しいUIパラダイムとなっています。
- 宣言型UIの構築:UI = f(State) — データが変更されると、UIが自動的に更新される
- ViewModel と StateFlow がリアクティブな UI 状態を管理し、設定の変更があっても安全に動作します
- Room + コルーチン + Flow によって、リアクティブなデータ層が構築される
- ComposeはKotlin DSLを使用してコンポーネントツリーを構築しており、XMLよりも簡潔です
- アクティビティのライフサイクルは依然としてAndroidの根幹をなしていますが、Composeではライフサイクルとの直接的なやり取りが軽減されます
📝 練習問題
- 初心者 (⭐): 「Hello, $name!」と表示する Compose
Greeting関数を作成してください。ヒント:@Composable fun Greeting(name: String) { Text("Hello, $name!") } - 中級 (⭐⭐): ViewModel と StateFlow を使用して、Compose UI でカウント数をリアルタイムに表示するカウンターを実装してください。ヒント:
MutableStateFlow(0)+collectAsState() - 上級 (⭐⭐⭐): 注文リストの「Compose」UIを完全に実装する:注文を表示するLazyColumn、プルして更新、クリックで詳細画面へ遷移。 ヒント:
LazyColumn+pullRefresh+NavController