iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Software Development

AI 時代的 Android Engineer:30 天打造我的 AI 開發工作流系列 第 15 篇

Day 15 - ViewModel可以保存狀態,所以什麼都放進ViewModel嗎?

  • 分享至 

  • xImage
  •  

昨天重新看了以前把 Timer 做成 Singleton 的設計,最後其實碰到一個更大的問題:

Android 裡的狀態,到底應該放在哪裡?

如果是 Fragment + ViewModel 的架構,一個很直覺的答案就是 ViewModel。

畫面需要的資料放 ViewModel、Fragment observe,這也是我自己的 Project 很常見的寫法。

但如果真的把所有 State 都往 ViewModel 塞,好像又怪怪的。

所以今天想重新整理的不是「ViewModel 怎麼用」,而是:

ViewModel 到底應該負責保存什麼?

先從 ViewModel 為什麼存在開始

假設直接把資料存在 Fragment:

private var elapsedMillis = 0L

只要 Fragment 被重新建立,原本這個 instance 裡的資料就可能消失。

ViewModel 解決的其中一個重要問題,就是讓 UI 相關資料不要直接綁死在 Activity 或 Fragment instance 上。

Fragment
   ↓
ViewModel
   ↓
UI 需要的資料

像螢幕旋轉造成 Activity / Fragment 重建時,同一個 ViewModel 可以繼續存在,新的 UI 再重新取得資料。

所以:

ViewModel 的生命週期比單次 Fragment instance 長。

但這裡有一個很容易產生的誤會:

活得比較久,是不是就代表重要資料都應該放這裡?

其實不是。

第一種:只跟畫面有關的狀態

例如一個畫面有:

Dialog 是否開啟
目前選到哪個 Tab
Loading 是否顯示
輸入中的內容

這些狀態的共同點是:

它們主要在描述「這個畫面現在長什麼樣子」。

這種資料放在 ViewModel 通常很好理解,尤其當它需要跨過畫面重建,或會影響其他畫面邏輯時。

例如:

使用者點擊設定
↓
ViewModel 更新狀態
↓
Fragment observe
↓
顯示 Dialog

Fragment 不需要自己保存一堆會影響畫面行為的變數。

但也不是任何小狀態都一定要丟進 ViewModel。

如果某個狀態只跟 View 本身短暫的呈現有關,而且消失後重新建立也沒關係,那留在 UI 層可能反而比較簡單。

所以判斷點不是:

這是不是 State?

而是:

這個 State 的生命週期和影響範圍是什麼?

第二種:功能本身的狀態

Timer 就開始不太一樣了。

例如:

現在是不是正在計時
目前是 Pomodoro 還是 Stopwatch
開始時間
已經經過多久

這些資料雖然最後會顯示在 UI,但它們並不是「畫面狀態」。

就算現在沒有任何 Fragment 顯示 Timer:

App 到背景
↓
畫面不存在
↓
Timer 還是在進行

所以:

畫面顯示 00:30

和:

Timer 已經經過 30 秒

其實是兩件不同的事情。

前者是 UI 呈現。

後者是功能本身的狀態。

如果把兩者混在一起,就很容易變成 ViewModel 既負責「畫面現在顯示什麼」,又負責「整個 App 現在的 Timer 到底在做什麼」。

這時就要開始問:

如果這個 ViewModel 消失了,這份 State 應不應該一起消失?

如果答案是不應該,那它可能就不該只存在這個 ViewModel。

第三種:需要真正保存的資料

還有另一種狀態生命週期更長。

例如 Timer 結束後建立的紀錄。

Timer
↓
完成
↓
建立紀錄
↓
寫入 Room

這筆紀錄不只是要跨 Fragment 重建,也不是 App 關掉就可以消失。

它需要跨過:

Fragment 重建
Activity 重建
Process 被殺掉
App 關閉
甚至手機重新開機

這時 ViewModel 當然就不是資料真正應該存在的地方。

所以如果按照「資料應該活多久」來看,可以粗略拆成:

短暫畫面狀態
→ UI / ViewModel

畫面之外仍存在的功能狀態
→ 更長生命週期的持有者

需要長期保存的資料
→ Database / Persistent Storage

這比單純問「要不要放 ViewModel」清楚很多。

ViewModel 還有一個很重要的界線:Process Death

這也是我覺得很容易混淆的地方。

ViewModel 可以跨過 Configuration Change,但不代表它可以永遠保存資料。

例如螢幕旋轉:

Activity 被重建
↓
ViewModel 保留
↓
新的 Activity 繼續使用

但如果 App 的 Process 被系統殺掉,記憶體裡的 ViewModel 也會一起消失。

所以:

Configuration Change
≠
Process Death

這兩件事情不能混在一起。

如果某份重要狀態只存在:

ViewModel
    ↓
MutableLiveData

那它能處理畫面重建,不代表能處理 Process Death。

這也是為什麼 Android 裡還會有 SavedStateHandle、資料庫或其他持久化方式。

它們解決的不是同一層問題。

SavedStateHandle 也不是拿來存所有東西

既然 ViewModel 遇到 Process Death 可能消失,那是不是把所有東西塞進 SavedStateHandle 就好了?

好像又回到同樣的問題。

例如使用者正在填一個搜尋條件,Process 被回收後,希望回來不要全部重填,這種狀態就很適合考慮恢復。

但如果是一整份從 API 拿回來的大量資料,就不一定要完整塞進去。

有時候真正需要保存的是:

query
selectedId
filter
目前操作到哪裡

Process 回來後,再根據這些資訊重新取得資料。

所以「恢復狀態」跟「保存所有資料」也是不同的事情。

我現在會先問的不是「要不要放 ViewModel」

如果今天重新設計一個畫面,我覺得自己會先問四個問題:

1. 誰需要這份 State?

2. 它應該活多久?

3. 它消失之後,可不可以重新取得?

4. Process 被殺掉後,需要恢復嗎?

例如:

Dialog 是否開啟
→ 畫面狀態

Timer 是否正在執行
→ 功能狀態

歷史計時紀錄
→ 持久化資料

三個都是 State,但生命週期完全不同。

所以它們也不應該因為:

ViewModel 可以保存 State

就全部被塞進同一個地方。

ViewModel 不是 State 的終點

以前在寫 MVVM 時,很容易形成一條很固定的思路:

UI 有資料
↓
放 ViewModel
↓
LiveData
↓
Fragment observe

這個方向本身沒有問題,但它只回答了「UI 怎麼取得資料」,沒有回答:

這份資料真正屬於誰?

我現在反而覺得這才是比較重要的問題。

ViewModel 很適合站在 UI 和其他層之間,把畫面需要的資料整理成 UI 可以使用的形式,也可以讓畫面相關狀態跨過 Configuration Change。

但它不是因為「比 Fragment 活得久」,就自然變成所有狀態的家。

決定 State 放在哪裡之前,先決定它應該跟誰一起活、跟誰一起消失。

昨天從 Singleton 開始想這件事,今天再看到 ViewModel,其實背後都是同一題:

State Ownership。

誰真正擁有這份狀態,可能比「我要用 LiveData、StateFlow 還是 ViewModel」更值得先想清楚。

下一集見:)


上一篇
Day 14 - 以前把 Timer 寫成 Singleton,現在的我還會這樣設計嗎?
下一篇
Day 16 - 畫面重建、Process Death、App被關掉,其實不是同一件事
系列文
AI 時代的 Android Engineer:30 天打造我的 AI 開發工作流 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言