Vibe coding全紀錄-25

A:

 好的,可以開始第一步「修改資料庫結構」,謝謝 

Q:

好的,我們立刻開始!

第一步「修改資料庫結構」是為新功能打下堅實地基的關鍵。我們將會新增一個資料表,並修改一個現有的資料表。

特別提醒: 這次的修改會變動到資料庫的「結構 (Schema)」。因此,我們需要處理一個重要的議題叫做「資料庫遷移 (Database Migration)」,我會在最後一步詳細說明如何用一個簡單的方式來處理它。


第十六步:修改資料庫結構

16.1 新增 AssetClass 枚舉 (Enum)

為了區分股票、債券等資產,我們先來定義這個新的分類。打開我們之前建立的 Enums.kt 檔案,在裡面加入 AssetClass

路徑: app/src/main/java/com/yourcompany/cashflow/data/local/entity/Enums.kt

Kotlin
package com.yourcompany.cashflow.data.local.entity

enum class AccountType { BANK, INVESTMENT, LIABILITY }
enum class TransactionType { INCOME, EXPENSE }
enum class FrequencyType { WEEKLY, MONTHLY, YEARLY }

// vvv-- 加入這個新的 Enum --vvv
enum class AssetClass {
    CASH,    // 現金 (銀行存款)
    STOCK,   // 股票
    BOND,    // 債券
    OTHER    // 其他資產
}

16.2 修改 Account 實體 (Entity)

現在,我們在 Account 資料表中加入 assetClass 這個新欄位。

路徑: app/src/main/java/com/yourcompany/cashflow/data/local/entity/Account.kt

Kotlin
package com.yourcompany.cashflow.data.local.entity

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "accounts")
data class Account(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val name: String,
    val type: AccountType,
    
    // vvv-- 加入這個新欄位 --vvv
    // 我們給它一個預設值,這樣 Room 在更新資料庫時,才知道要為舊資料填入什麼內容
    @ColumnInfo(defaultValue = "CASH")
    val assetClass: AssetClass, 
    
    val initialBalance: Double
)

16.3 建立 AccountSnapshot 實體 (Entity)

這是我們用來儲存每月資產快照的全新資料表。

路徑: app/src/main/java/com/yourcompany/cashflow/data/local/entity/AccountSnapshot.kt (新檔案)

Kotlin
package com.yourcompany.cashflow.data.local.entity

import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(
    tableName = "account_snapshots",
    foreignKeys = [ForeignKey(
        entity = Account::class,
        parentColumns = ["id"],
        childColumns = ["accountId"],
        onDelete = ForeignKey.CASCADE // 如果帳戶被刪除,其所有快照也一併刪除
    )]
)
data class AccountSnapshot(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val accountId: Long,    // 關聯到哪個帳戶
    val snapshotDate: Long, // 快照日期 (儲存時間戳)
    val value: Double       // 該日期的帳戶價值
)

16.4 建立 SnapshotDao 介面

我們需要一個新的 DAO 來操作新的 account_snapshots 資料表。

路徑: app/src/main/java/com/yourcompany/cashflow/data/local/dao/SnapshotDao.kt (新檔案)

Kotlin
package com.yourcompany.cashflow.data.local.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy

import com.yourcompany.cashflow.data.local.entity.AccountSnapshot

@Dao
interface SnapshotDao {

    // 我們先定義一個最基本的新增方法
    // 之後會再回來加入更複雜的查詢方法
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(snapshot: AccountSnapshot)

}

16.5 更新 AppDatabase 並處理資料庫遷移

這是最關鍵的一步。我們需要讓 AppDatabase 知道有新的資料表和 DAO,並且處理版本升級。

A. 更新 AppDatabase.kt

Kotlin
// /data/local/AppDatabase.kt
package com.yourcompany.cashflow.data.local

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.yourcompany.cashflow.data.local.dao.*
import com.yourcompany.cashflow.data.local.entity.*

@Database(
    entities = [
        Account::class,
        MainCategory::class,
        SubCategory::class,
        Transaction::class,
        RecurringTransaction::class,
        AccountSnapshot::class // <-- 1. 加入新的 Entity
    ],
    version = 2, // <-- 2. 非常重要!將版本號從 1 改為 2
    exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun accountDao(): AccountDao
    abstract fun categoryDao(): CategoryDao
    abstract fun transactionDao(): TransactionDao
    abstract fun recurringTransactionDao(): RecurringTransactionDao
    abstract fun snapshotDao(): SnapshotDao // <-- 3. 加入新的 DAO
}

B. 處理資料庫遷移 (Database Migration)

由於我們修改了資料庫結構(增加了表格、修改了欄位)並提升了 version,我們必須告訴 Room 如何從舊版 (v1) 過渡到新版 (v2)。若不處理,App 在更新後一啟動就會閃退。

在正式上架的 App 中,我們會寫詳細的遷移腳本來保留使用者資料。但在開發階段,有一個更簡單直接的方法:刪除舊資料庫,重新建立一個新的。

我們需要在 Hilt 的 AppModule 中設定它。

路徑: app/src/main/java/com/yourcompany/cashflow/di/AppModule.kt

Kotlin
// /di/AppModule.kt
...
object AppModule {

    @Provides
    @Singleton
    fun provideAppDatabase(app: Application): AppDatabase {
        return Room.databaseBuilder(
            app,
            AppDatabase::class.java,
            "cash_flow_db"
        )
        .fallbackToDestructiveMigration() // <-- 加入這一行!
        .build()
    }

    // ... 其他 provide 方法 ...

    // vvv-- 為新的 DAO 加入 Provider --vvv
    @Provides
    @Singleton
    fun provideSnapshotDao(db: AppDatabase): SnapshotDao = db.snapshotDao()
}

fallbackToDestructiveMigration() 這行程式碼的意思是:「如果需要版本升級,且沒有提供遷移腳本,就直接刪除所有資料並重建資料庫。」這在開發時非常方便,但請記住,發布 App 前需要換成正式的遷移方案,以免清除使用者的所有資料!

C. 更新 Converters.kt

最後,別忘了為我們新的 AssetClass enum 加入 TypeConverter。

Kotlin
// /data/local/Converters.kt
...
class Converters {
    // ... 其他 Converters ...
    
    @TypeConverter fun fromAssetClass(value: AssetClass) = value.name
    @TypeConverter fun toAssetClass(value: String) = AssetClass.valueOf(value)
}

結構修改完成!

我們已經成功地更新了資料庫的藍圖,現在它已經準備好儲存我們資產負債表所需要的「每月快照」資料了。

資料庫的基礎建設已經完成。下一步,我們將建立 BalanceSheetViewModel 和對應的 Repository,並在其中實作我們需要的複雜查詢邏輯,來為我們的圖表準備數據。準備好了嗎?