Kotlin: KotlinによるAndroid開発の基礎

最終更新:2026-08-26

Android開発ではKotlinが全面的に採用されています。Jetpack ComposeはKotlin DSLを用いて宣言型のUIを構築し、ViewModelとStateFlowがリアクティブな状態を管理し、Roomとコルーチンがデータ層を簡素化します。CharlieはAndroid用の注文ダッシュボードを構築しています。

1. 学習内容


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) ライフサイクル状態図

100%
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) レンダリングパイプラインの構築

100%
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 以降では、型安全なナビゲーションが利用可能です。

📖 まとめ


📝 練習問題

  1. 初心者 (⭐): 「Hello, $name!」と表示する Compose Greeting 関数を作成してください。ヒント: @Composable fun Greeting(name: String) { Text("Hello, $name!") }
  2. 中級 (⭐⭐): ViewModel と StateFlow を使用して、Compose UI でカウント数をリアルタイムに表示するカウンターを実装してください。ヒント: MutableStateFlow(0) + collectAsState()
  3. 上級 (⭐⭐⭐): 注文リストの「Compose」UIを完全に実装する:注文を表示するLazyColumn、プルして更新、クリックで詳細画面へ遷移。 ヒント:LazyColumn + pullRefresh + NavController

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%