← Kotlin / Android Course

ANDROID CORE · 110

Lifecycle / ViewModel / UI State:誰活多久?誰擁有 State?

Android app 不是 main() 從第一行跑到最後一行。Framework 依系統與使用者事件建立、暫停、重建 UI component。真正要學的是 lifecycle ownership,而不是死背一條固定 callback 順序。

Why now

107–109 已經建立 Trip Meter domain state 與 suspend repository。現在要把它接進 Android screen,而不讓 Activity/Composable 同時變成 business logic、network client、GPS manager、state store。

Android framework建立 UIViewModel呼叫TripRepository產生TripStaterenderscreen

Learning outcomes

1. Lifecycle 是 state machine,不是背一本道路

Created
  ↓
Started
  ↓
Resumed

可能因情境:
Resumed → Paused
Started → Stopped
Component 被重建
Process 甚至被系統終止

實際 callback 路徑依 navigation、configuration、multi-window、system reclaim 等情況而變。不要假設每次都完整走同一條。

2. UI component 不應成為唯一 state source

class TripActivity : Activity() {
    var distanceKm = 0.0
}

若重要 screen state 只存在 UI component field,重建 component 時就容易遺失或重做昂貴工作。

3. ViewModel:screen-level state owner

data class TripScreenState(
    val loading: Boolean = false,
    val trip: TripState? = null,
    val error: String? = null
)

class TripViewModel(
    private val repository: TripRepository
) : ViewModel() {

    private val _state =
        MutableStateFlow(
            TripScreenState()
        )

    val state:
        StateFlow<TripScreenState>
        = _state
}

ViewModel 不是 Database,也不是永遠活著;它主要讓 screen-level state / coordination 跨某些 UI recreation 存活並與 lifecycle 整合。

4. StateFlow:UI 訂閱 state,不直接控制 repository internals

fun refresh() {
    viewModelScope.launch {
        _state.value =
            TripScreenState(
                loading = true
            )

        val result =
            runCatching {
                repository
                    .loadSnapshot()
            }

        _state.value =
            result.fold(
                onSuccess = {
                    TripScreenState(
                        trip = it
                    )
                },
                onFailure = {
                    TripScreenState(
                        error =
                            it.message
                    )
                }
            )
    }
}
User actionViewModelviewModelScopeRepositoryStateFlowUI

5. Derived UI data 不必全部塞進 state

val speedText =
    state.trip
        ?.let(::displaySpeed)
        ?: "--"

如果值可以由 canonical state 直接算出,就不一定要額外保存一份,避免同步問題。

6. Configuration change ≠ Process death

Configuration / UI recreation

ViewModel 常可保留 screen state。

Process death

整個 process memory 可能消失;需要 SavedState / persistent storage 才能真正恢復。

Project checkpoint:Trip Meter Android Shell v1

Activity / Compose UI
   ↓ observes
TripScreenState
   ↑
TripViewModel
   ↓ suspend call
TripRepository
   ↓
Trip domain core

下一課加入 location permission 與 location provider,會出現另一個 state machine。

Debug evidence:旋轉後重複 request

  1. request 是在 UI creation 每次直接啟動,還是 ViewModel 管理?
  2. state owner 是誰?
  3. 是否把 side effect 綁到會重複 composition/recreation 的位置?
  4. 舊 job 有沒有取消?

7. Current state-saving model:ViewModel、rememberSaveable、SavedStateHandle、Persistence

不要把所有 state 都丟進 ViewModel。現在比較可靠的判斷方式是先問:這個 state 是 UI element state、screen business state,還是 durable domain data?

State 類型典型 owner / mechanism
輕量 UI element staterememberSaveable / saved instance state
Screen-level business stateViewModel
Screen state 需要跨 system-initiated process death 恢復SavedStateHandle 作為 backup
真正 durable domain dataDatabase / file / repository persistence

ViewModel 會跨 configuration change 保留,但不會在 system-initiated process death 後保留同一個 in-memory instance。SavedStateHandle 則適合保存輕量、可重建 screen state;它仍不是永久資料庫。

Rotation
  Activity recreated
  ViewModel retained

Process death
  memory gone
  SavedStateHandle / rememberSaveable may restore lightweight state

Reboot / task removal / durable history
  persistent data layer

8. Lifecycle evidence 不只 callback name

Debug lifecycle 問題時,最重要的是把「誰被重建、誰仍存在、哪個 side effect 在哪個 scope 啟動」記錄下來。只看一條 callback log 很容易把 configuration change 誤認為 process death。

Evidence:
Activity instance id
ViewModel instance id
process id
saved-state keys
repository request id
job start / cancel time

如果 Activity id 變、ViewModel id 不變,較像 configuration recreation;如果 process id 與 ViewModel 都換了,就要往 process recreation / saved-state recovery 查。

Knowledge check

  1. 為什麼 Activity lifecycle 不是固定一本道路?
  2. ViewModel 能保證 process death 後資料一定還在嗎?
  3. 哪些是 canonical state,哪些可 derive?
  4. 畫出 UI → ViewModel → Repository → state → UI 的資料流。