A:
好的,謝謝你提出這個計畫,就請開始階段A的第十六步:為「專案」功能更新資料庫與數據層,謝謝
Q:
好的,我們立刻開始執行新藍圖的階段 A。
這一步的目標很明確:我們要再次修改資料庫,讓它能夠完整地支援「專案」功能,特別是專案的「狀態」(進行中/已完成),並建立起對應的數據操作管道。
第十六步:為「專案」功能更新資料庫與數據層
16.1 建立 ProjectStatus
枚舉 (Enum)
首先,我們定義專案的兩種狀態。打開 Enums.kt
檔案,在裡面加入 ProjectStatus
。
路徑: app/src/main/java/com/yourcompany/cashflow/data/local/entity/Enums.kt
package com.yourcompany.cashflow.data.local.entity
// ... (現有的 AccountType, TransactionType, FrequencyType, AssetClass)
// vvv-- 加入這個新的 Enum --vvv
enum class ProjectStatus {
ACTIVE, // 進行中
COMPLETED // 已完成
}
16.2 建立 Project
實體 (Entity)
我們在專案初期已經規劃了這個 Entity,現在我們正式建立它,並加入新的 status
欄位。
路徑: app/src/main/java/com/yourcompany/cashflow/data/local/entity/Project.kt
(新檔案)
package com.yourcompany.cashflow.data.local.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "projects")
data class Project(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
val budget: Double,
val description: String?,
// 我們為新欄位提供預設值,這對之後的資料庫遷移很重要
@ColumnInfo(defaultValue = "ACTIVE")
val status: ProjectStatus
)
16.3 建立 ProjectDao.kt
介面
這個 DAO 將負責所有對 projects
資料表的直接操作。
路徑: app/src/main/java/com/yourcompany/cashflow/data/local/dao/ProjectDao.kt
(新檔案)
package com.yourcompany.cashflow.data.local.dao
import androidx.room.*
import com.yourcompany.cashflow.data.local.entity.Project
import com.yourcompany.cashflow.data.local.entity.ProjectStatus
import kotlinx.coroutines.flow.Flow
@Dao
interface ProjectDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(project: Project)
@Update
suspend fun update(project: Project)
@Delete
suspend fun delete(project: Project)
@Query("SELECT * FROM projects WHERE id = :id")
fun getProjectById(id: Long): Flow<Project?>
@Query("SELECT * FROM projects ORDER BY name ASC")
fun getAllProjects(): Flow<List<Project>>
// 這個查詢將用於「新增交易」畫面,只顯示進行中的專案
@Query("SELECT * FROM projects WHERE status = :status ORDER BY name ASC")
fun getProjectsByStatus(status: ProjectStatus): Flow<List<Project>>
}
16.4 建立 ProjectRepository.kt
建立 Repository 來封裝 DAO 的操作。
路徑: app/src/main/java/com/yourcompany/cashflow/data/repository/ProjectRepository.kt
(新檔案)
package com.yourcompany.cashflow.data.repository
import com.yourcompany.cashflow.data.local.dao.ProjectDao
import com.yourcompany.cashflow.data.local.entity.Project
import com.yourcompany.cashflow.data.local.entity.ProjectStatus
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
interface ProjectRepository {
suspend fun insert(project: Project)
suspend fun update(project: Project)
suspend fun delete(project: Project)
fun getProjectById(id: Long): Flow<Project?>
fun getAllProjects(): Flow<List<Project>>
fun getActiveProjects(): Flow<List<Project>>
}
class ProjectRepositoryImpl @Inject constructor(
private val projectDao: ProjectDao
) : ProjectRepository {
override suspend fun insert(project: Project) = projectDao.insert(project)
override suspend fun update(project: Project) = projectDao.update(project)
override suspend fun delete(project: Project) = projectDao.delete(project)
override fun getProjectById(id: Long): Flow<Project?> = projectDao.getProjectById(id)
override fun getAllProjects(): Flow<List<Project>> = projectDao.getAllProjects()
override fun getActiveProjects(): Flow<List<Project>> = projectDao.getProjectsByStatus(ProjectStatus.ACTIVE)
}
16.5 最終整合:更新 AppDatabase
與 DI 模組
我們再次需要更新資料庫主體,並處理版本升級。
A. 更新 AppDatabase.kt
// /data/local/AppDatabase.kt
...
import com.yourcompany.cashflow.data.local.entity.Project // 匯入新的 Entity
@Database(
entities = [
// ... (現有的所有 Entities)
AccountSnapshot::class,
Project::class // <-- 1. 加入新的 Project Entity
],
version = 3, // <-- 2. 非常重要!將版本號從 2 改為 3
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
// ... (現有的所有 DAOs)
abstract fun snapshotDao(): SnapshotDao
abstract fun projectDao(): ProjectDao // <-- 3. 加入新的 DAO
}
B. 更新 AppModule.kt 和 RepositoryModule.kt
我們需要讓 Hilt 知道如何建立 ProjectDao 和 ProjectRepository。
路徑: app/src/main/java/com/yourcompany/cashflow/di/AppModule.kt
// ...
object AppModule {
@Provides
@Singleton
fun provideAppDatabase(app: Application): AppDatabase {
// fallbackToDestructiveMigration() 會再次幫我們處理 v2 到 v3 的升級
return Room.databaseBuilder(...)
.fallbackToDestructiveMigration()
.build()
}
// ... (其他 provide 方法)
// vvv-- 為新的 DAO 加入 Provider --vvv
@Provides
@Singleton
fun provideProjectDao(db: AppDatabase): ProjectDao = db.projectDao()
}
路D徑: app/src/main/java/com/yourcompany/cashflow/di/RepositoryModule.kt
// ...
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
// ... (其他 Binds)
// vvv-- 加入 ProjectRepository 的綁定 --vvv
@Binds
@Singleton
abstract fun bindProjectRepository(impl: ProjectRepositoryImpl): ProjectRepository
}
C. 更新 Converters.kt
為新的 ProjectStatus enum 加入 TypeConverter。
路徑: app/src/main/java/com/yourcompany/cashflow/data/local/Converters.kt
// ...
class Converters {
// ... (其他 Converters)
@TypeConverter fun fromProjectStatus(value: ProjectStatus) = value.name
@TypeConverter fun toProjectStatus(value: String) = ProjectStatus.valueOf(value)
}
基礎建設完成!
我們已經成功地在資料庫層級,為「專案」功能建立起了完整的骨架。我們的 App 現在已經具備了儲存、管理、讀取專案及其狀態的能力。
所有後台的準備工作都已就緒。
下一步,第十七步,我們將會建立對應的 ProjectViewModel
和使用者介面 (ProjectListScreen
, AddEditProjectScreen
),讓使用者可以真正地在畫面上與這些專案數據進行互動。準備好進入 UI 的部分了嗎?