A:
請幫忙列出到現在應該建立的所有檔案與內容還有檔案路徑架構,謝謝
Q:
好的,當然!在我們準備進入 UI 開發之前,這是一個絕佳的時機來盤點和整理目前為止所有的程式碼。這能幫助你建立一個清晰的專案全貌。
我會將所有必要的檔案,按照一個標準、有組織的專案結構來列出。你可以對照這個結構來檢查或建立你的檔案。
「錢流」App 專案檔案總覽
專案根目錄結構
這是一個推薦的套件 (package) 結構,能讓你的專案保持整潔:
└── app/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── yourcompany/ (通常是你的網域名稱倒置)
│ │ │ └── cashflow/
│ │ │ ├── di/ (依賴注入模組)
│ │ │ ├── data/ (所有數據相關)
│ │ │ │ ├── local/ (本地數據,主要放 Room 相關)
│ │ │ │ │ ├── dao/
│ │ │ │ │ └── entity/
│ │ │ │ ├── model/ (資料組合物件)
│ │ │ │ └── repository/
│ │ │ ├── ui/ (UI 相關)
│ │ │ │ └── viewmodel/
│ │ │ └── CashFlowApp.kt (Application 類別)
│ │ └── AndroidManifest.xml
│
└── build.gradle.kts (或 build.gradle)
1. 設定檔 (Configuration Files)
app/build.gradle.kts
// 確保 plugins { ... } 區塊中有這些
plugins {
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
}
// 確保 dependencies { ... } 區塊中有這些
dependencies {
// Room
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
// Hilt
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-android-compiler:2.51.1")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
// ... 其他 Lifecycle, Compose 等依賴
}
app/src/main/AndroidManifest.xml
<application
android:name=".CashFlowApp" ... >
...
</application>
2. 根目錄套件
app/src/main/java/com/yourcompany/cashflow/CashFlowApp.kt
package com.yourcompany.cashflow
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class CashFlowApp : Application()
3. DI (Dependency Injection) 套件
app/src/main/java/com/yourcompany/cashflow/di/AppModule.kt
package com.yourcompany.cashflow.di
import android.app.Application
import androidx.room.Room
import com.yourcompany.cashflow.data.local.AppDatabase
import com.yourcompany.cashflow.data.local.dao.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideAppDatabase(app: Application): AppDatabase {
return Room.databaseBuilder(
app,
AppDatabase::class.java,
"cash_flow_db"
).build()
}
@Provides
@Singleton
fun provideTransactionDao(db: AppDatabase): TransactionDao = db.transactionDao()
@Provides
@Singleton
fun provideCategoryDao(db: AppDatabase): CategoryDao = db.categoryDao()
@Provides
@Singleton
fun provideAccountDao(db: AppDatabase): AccountDao = db.accountDao()
@Provides
@Singleton
fun provideRecurringTransactionDao(db: AppDatabase): RecurringTransactionDao = db.recurringTransactionDao()
}
app/src/main/java/com/yourcompany/cashflow/di/RepositoryModule.kt
package com.yourcompany.cashflow.di
import com.yourcompany.cashflow.data.repository.TransactionRepository
import com.yourcompany.cashflow.data.repository.TransactionRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindTransactionRepository(
transactionRepositoryImpl: TransactionRepositoryImpl
): TransactionRepository
}
4. Data 套件
data/local/entity/
在這個資料夾底下,包含所有資料表 (Entity) 和 enum
的定義。
<details>
<summary>點此展開 Entity 檔案內容</summary>
Account.kt
package com.yourcompany.cashflow.data.local.entity
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,
val initialBalance: Double
)
MainCategory.kt
package com.yourcompany.cashflow.data.local.entity
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "main_categories",
indices = [Index(value = ["name"], unique = true)])
data class MainCategory(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
val type: TransactionType
)
SubCategory.kt
package com.yourcompany.cashflow.data.local.entity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "sub_categories",
foreignKeys = [ForeignKey(
entity = MainCategory::class,
parentColumns = ["id"],
childColumns = ["mainCategoryId"],
onDelete = ForeignKey.CASCADE
)],
indices = [Index(value = ["mainCategoryId", "name"], unique = true)])
data class SubCategory(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val mainCategoryId: Long,
val name: String
)
Transaction.kt
package com.yourcompany.cashflow.data.local.entity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "transactions",
foreignKeys = [
ForeignKey(entity = Account::class, parentColumns = ["id"], childColumns = ["accountId"], onDelete = ForeignKey.RESTRICT),
ForeignKey(entity = SubCategory::class, parentColumns = ["id"], childColumns = ["subCategoryId"], onDelete = ForeignKey.RESTRICT)
])
data class Transaction(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val accountId: Long,
val subCategoryId: Long,
val amount: Double,
val type: TransactionType,
val transactionDate: Long,
val note: String?
)
RecurringTransaction.kt
package com.yourcompany.cashflow.data.local.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "recurring_transactions")
data class RecurringTransaction(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val accountId: Long,
val subCategoryId: Long,
val amount: Double,
val type: TransactionType,
val note: String?,
val frequency: FrequencyType,
val dayOfWeek: Int?,
val dayOfMonth: Int?,
val monthOfYear: Int?,
val dayOfYear: Int?,
val startDate: Long,
val endDate: Long?
)
Enums.kt (可以將所有 enum 放在一個檔案)
package com.yourcompany.cashflow.data.local.entity
enum class AccountType { BANK, INVESTMENT, LIABILITY }
enum class TransactionType { INCOME, EXPENSE }
enum class FrequencyType { WEEKLY, MONTHLY, YEARLY }
</details>
data/local/dao/
<details>
<summary>點此展開 DAO 檔案內容</summary>
AccountDao.kt
package com.yourcompany.cashflow.data.local.dao
import androidx.room.*
import com.yourcompany.cashflow.data.local.entity.Account
import kotlinx.coroutines.flow.Flow
@Dao
interface AccountDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(account: Account)
@Query("SELECT * FROM accounts ORDER BY name ASC")
fun getAllAccounts(): Flow<List<Account>>
}
CategoryDao.kt
package com.yourcompany.cashflow.data.local.dao
import androidx.room.*
import com.yourcompany.cashflow.data.local.entity.MainCategory
import com.yourcompany.cashflow.data.local.entity.SubCategory
import com.yourcompany.cashflow.data.local.entity.TransactionType
import kotlinx.coroutines.flow.Flow
@Dao
interface CategoryDao {
@Insert
suspend fun insertMainCategory(category: MainCategory)
@Insert
suspend fun insertSubCategory(category: SubCategory)
@Query("SELECT * FROM main_categories WHERE type = :type")
fun getAllMainCategoriesByType(type: TransactionType): Flow<List<MainCategory>>
@Query("SELECT * FROM sub_categories WHERE mainCategoryId = :mainCategoryId")
fun getSubCategoriesForMain(mainCategoryId: Long): Flow<List<SubCategory>>
}
TransactionDao.kt
package com.yourcompany.cashflow.data.local.dao
import androidx.room.*
import com.yourcompany.cashflow.data.local.entity.Transaction
import com.yourcompany.cashflow.data.model.TransactionWithDetails
import kotlinx.coroutines.flow.Flow
@Dao
interface TransactionDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(transaction: Transaction)
@androidx.room.Transaction
@Query("SELECT * FROM transactions ORDER BY transactionDate DESC")
fun getAllTransactionDetails(): Flow<List<TransactionWithDetails>>
}
RecurringTransactionDao.kt
package com.yourcompany.cashflow.data.local.dao
import androidx.room.*
import com.yourcompany.cashflow.data.local.entity.RecurringTransaction
import kotlinx.coroutines.flow.Flow
@Dao
interface RecurringTransactionDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(recurringTransaction: RecurringTransaction)
@Query("SELECT * FROM recurring_transactions")
fun getAllRecurringTransactions(): Flow<List<RecurringTransaction>>
}
</details>
data/local/
<details>
<summary>點此展開 Database 和 Converters 檔案內容</summary>
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
],
version = 1,
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
}
Converters.kt
package com.yourcompany.cashflow.data.local
import androidx.room.TypeConverter
import com.yourcompany.cashflow.data.local.entity.AccountType
import com.yourcompany.cashflow.data.local.entity.FrequencyType
import com.yourcompany.cashflow.data.local.entity.TransactionType
class Converters {
@TypeConverter fun fromTransactionType(value: TransactionType) = value.name
@TypeConverter fun toTransactionType(value: String) = TransactionType.valueOf(value)
@TypeConverter fun fromFrequencyType(value: FrequencyType) = value.name
@TypeConverter fun toFrequencyType(value: String) = FrequencyType.valueOf(value)
@TypeConverter fun fromAccountType(value: AccountType) = value.name
@TypeConverter fun toAccountType(value: String) = AccountType.valueOf(value)
}
</details>
data/model/
<details>
<summary>點此展開 Model 檔案內容</summary>
TransactionWithDetails.kt
package com.yourcompany.cashflow.data.model
import androidx.room.Embedded
import androidx.room.Relation
import com.yourcompany.cashflow.data.local.entity.Account
import com.yourcompany.cashflow.data.local.entity.SubCategory
import com.yourcompany.cashflow.data.local.entity.Transaction
data class TransactionWithDetails(
@Embedded
val transaction: Transaction,
@Relation(parentColumn = "subCategoryId", entityColumn = "id")
val subCategory: SubCategory,
@Relation(parentColumn = "accountId", entityColumn = "id")
val account: Account
)
</details>
data/repository/
<details>
<summary>點此展開 Repository 檔案內容</summary>
TransactionRepository.kt
package com.yourcompany.cashflow.data.repository
import com.yourcompany.cashflow.data.local.entity.Transaction
import com.yourcompany.cashflow.data.model.TransactionWithDetails
import kotlinx.coroutines.flow.Flow
interface TransactionRepository {
fun getAllTransactionDetails(): Flow<List<TransactionWithDetails>>
suspend fun insertTransaction(transaction: Transaction)
}
TransactionRepositoryImpl.kt
package com.yourcompany.cashflow.data.repository
import com.yourcompany.cashflow.data.local.dao.TransactionDao
import com.yourcompany.cashflow.data.local.entity.Transaction
import com.yourcompany.cashflow.data.model.TransactionWithDetails
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class TransactionRepositoryImpl @Inject constructor(
private val transactionDao: TransactionDao
) : TransactionRepository {
override fun getAllTransactionDetails(): Flow<List<TransactionWithDetails>> {
return transactionDao.getAllTransactionDetails()
}
override suspend fun insertTransaction(transaction: Transaction) {
transactionDao.insert(transaction)
}
}
</details>
5. UI 套件
ui/viewmodel/
<details>
<summary>點此展開 ViewModel 檔案內容</summary>
TransactionViewModel.kt
package com.yourcompany.cashflow.ui.viewmodel
import androidx.lifecycle.ViewModel
import com.yourcompany.cashflow.data.repository.TransactionRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class TransactionViewModel @Inject constructor(
private val repository: TransactionRepository
) : ViewModel() {
val allTransactions = repository.getAllTransactionDetails()
// 之後會在這裡加入更多 UI 相關的邏輯與狀態
}
</details>
這就是我們到目前為止建立的所有後台架構檔案。有了這份清單,你就可以確保專案的完整性。接下來,我們就可以充滿信心地開始建構使用者介面 (UI) 了!