A:
好的,請開始進行,第二十二步階段A的第三步建立BudgetViewModel,謝謝
Q:
好的,我們來進行階段 A 的第三步,也是後端邏輯的最後一步:建立 BudgetViewModel。
這個 ViewModel 將扮演「大廚」的角色。它會:
從數據層 (DAO) 取得我們需要的食材——「最新淨值」。
接收使用者透過 UI 設定的調味料——「規劃報酬率」、「通貨膨漲率」、「剩餘年數」。
使用我們準備好的食譜——
PMT公式。將以上所有東西結合,烹調出最終的美味佳餚——「年度可支配預算」,並將其呈現在
StateFlow中,隨時準備上菜給 UI。
第廿二步,階段 A-3:建立 BudgetViewModel
路徑: app/src/main/java/com/yourcompany/cashflow/ui/viewmodel/BudgetViewModel.kt (新檔案)
<details>
<summary><strong>點此展開 BudgetViewModel.kt 的完整程式碼</strong></summary>
package com.yourcompany.cashflow.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.yourcompany.cashflow.data.model.NetWorth
import com.yourcompany.cashflow.data.repository.SnapshotRepository
import com.yourcompany.cashflow.util.FinancialFormulas
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class BudgetViewModel @Inject constructor(
private val snapshotRepository: SnapshotRepository
) : ViewModel() {
// --- 1. 狀態管理:使用者輸入的參數 ---
// 使用 MutableStateFlow 來保存使用者可以透過 UI 調整的值
// 規劃報酬率 (例如: 0.05f 代表 5%)
private val _plannedReturnRate = MutableStateFlow(0.05f)
val plannedReturnRate: StateFlow<Float> = _plannedReturnRate.asStateFlow()
// 通貨膨漲率 (例如: 0.02f 代表 2%)
private val _inflationRate = MutableStateFlow(0.02f)
val inflationRate: StateFlow<Float> = _inflationRate.asStateFlow()
// 剩餘年數 (例如: 30 年)
private val _remainingYears = MutableStateFlow(30)
val remainingYears: StateFlow<Int> = _remainingYears.asStateFlow()
// --- 2. 狀態管理:從資料庫取得的數據 ---
// 從 Repository 取得最新的淨值數據流
private val latestNetWorth: StateFlow<NetWorth?> = snapshotRepository.getLatestNetWorth()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
// --- 3. 響應式計算:最終的預算結果 ---
/**
* 這是 ViewModel 的核心。
* 使用 `combine` 將所有相關的數據流(來自資料庫和使用者輸入)結合在一起。
* 當其中任何一個數據流發生變化時,這個區塊會自動重新執行計算。
*/
val annualDisposableBudget: StateFlow<Double> = combine(
latestNetWorth,
plannedReturnRate,
inflationRate,
remainingYears
) { netWorth, returnRate, inflation, years ->
// 如果淨值不存在,預算為 0
if (netWorth == null || netWorth.value <= 0) {
return@combine 0.0
}
// 1. 計算實質報酬率
val realRateOfReturn = (1 + returnRate) / (1 + inflation) - 1
// 2. 呼叫我們建立的 PMT 工具進行計算
FinancialFormulas.pmt(
rate = realRateOfReturn.toDouble(),
nper = years,
pv = netWorth.value // 使用淨值作為現值
)
}.stateIn(viewModelScope, SharingStarted.WhileSubsided(5000), 0.0) // 轉換為 StateFlow 並提供初始值
// --- 4. 公開的動作:讓 UI 可以更新狀態 ---
fun setPlannedReturnRate(rate: Float) {
_plannedReturnRate.value = rate
}
fun setInflationRate(rate: Float) {
_inflationRate.value = rate
}
fun setRemainingYears(years: Int) {
_remainingYears.value = years
}
}
</details>
程式碼說明
狀態管理 (State Management):
我們為使用者可以調整的三個參數(報酬率、通膨率、年數)分別建立了 MutableStateFlow。這允許我們在 ViewModel 內部修改它們,同時對外暴露一個不可變的 StateFlow 供 UI 觀察。
響應式計算 (Reactive Calculation):
annualDisposableBudget 的計算是這個 ViewModel 最精華的部分。combine 運算子是 Kotlin Flow 中一個非常強大的工具。你可以把它想像成一個 Excel 的公式:它會監控 A1 (latestNetWorth)、B1 (plannedReturnRate)、C1 (inflationRate)、D1 (remainingYears) 這四個儲存格。只要其中任何一個儲存格的值發生改變,這個公式就會自動重新計算一次,並將結果更新到它所在的儲存格 (annualDisposableBudget)。這讓我們無需手動觸發任何「計算」按鈕,一切都是即時、自動反應的。
公開的動作 (Public Actions):
set... 開頭的三個函式,是我們提供給 UI 的「遙控器按鈕」。當使用者在畫面上拖動滑桿或輸入數字時,UI 就會呼叫這些函式來更新 ViewModel 中的狀態,進而觸發 combine 的自動重新計算。
後端邏輯完成!
我們已經完成了預算規劃功能的「大腦」。這個 BudgetViewModel 現在是一個完全獨立、可運作的邏輯核心。它已經準備好接收使用者的參數,並從資料庫中提取數據,然後自動計算出最終的預算建議。
所有精密的計算都已在幕後備妥,下一步,就是進入階段 B,建立一個美觀且直觀的使用者介面 (BudgetPlanningScreen),將這個強大的計算結果呈現給使用者。
準備好開始打造這個功能的 UI 了嗎?