Vibe coding全紀錄-7

A:

 好的,讓我們開始一步一步慢慢分析來開始如何使用 Hilt 來自動化管理這些元件之間的依賴關係,讓我們整個架構無縫地運作起來,謝謝 

Q:

好的,我們來進行最關鍵的整合步驟:使用 Hilt 將我們建立的所有元件(Database, DAO, Repository)串連起來。

把 Hilt 想像成一個聰明絕頂的「專案管家」。我們不用再手動去建立每一個物件,然後把它傳給需要它的地方(例如手動建立 TransactionDao,再建立 Repository 並把 Dao 傳進去)。

我們只需要告訴 Hilt 兩件事:

  1. 如何製作每一樣東西(例如如何建立資料庫)。

  2. 誰需要哪一樣東西(例如 ViewModel 需要 Repository)。

Hilt 就會自動在對的時機,把對的東西,交到對的人手上。這個過程就叫做 依賴注入 (Dependency Injection, DI)


第六步:設定 Hilt 依賴注入

1. 加入 Hilt 相關的 Gradle 依賴

這是使用任何函式庫的第一步。我們需要分別在兩個 build.gradle.kts 檔案中加入設定。

A. 在專案層級的 build.gradle.kts 檔案中

這個檔案在你的專案根目錄下。我們需要加入 Hilt 的 Gradle 外掛。

Kotlin
// build.gradle.kts (Project: CashFlow)

plugins {
    // ... 其他外掛
    id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

B. 在 App 模組層級的 build.gradle.kts 檔案中

這個檔案在 app 資料夾底下。

Kotlin
// build.gradle.kts (Module: app)

plugins {
    // ... 其他外掛
    id("kotlin-kapt") // 加入 kapt 外掛
    id("com.google.dagger.hilt.android")
}

android {
    // ...
}

dependencies {
    // ... 其他依賴

    // Hilt 的核心依賴
    implementation("com.google.dagger:hilt-android:2.51.1")
    kapt("com.google.dagger:hilt-android-compiler:2.51.1")

    // (選用) 如果要在 ViewModel 中使用 Hilt,需要額外加入這個
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}

重要: 修改完 Gradle 檔案後,Android Studio 頂部會出現一個提示條,請務必點擊 "Sync Now" 來讓設定生效。

2. 設定應用程式進入點 (Application Class)

我們需要一個自訂的 Application 類別,並用 @HiltAndroidApp 註解來告訴 Hilt:「這是我們 App 的主容器,請開始在這裡進行依賴注入的管理。」

A. 建立 CashFlowApp.kt 檔案

Kotlin
import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class CashFlowApp : Application() {
    // 目前這個類別裡面不需要寫任何程式碼
    // Hilt 會在背景自動產生它需要的東西
}

B. 在 AndroidManifest.xml 中註冊這個類別

打開 app/src/main/AndroidManifest.xml 檔案,在 <application> 標籤中加入 android:name 屬性。

XML
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>

    <application
        android:name=".CashFlowApp" android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.CashFlow">
        <activity ...>
            ...
        </activity>
    </application>

</manifest>

3. 建立 Hilt 模組 (Module) - 教 Hilt 如何「製作」元件

Hilt 知道如何建立我們用 @Inject constructor 標記的類別 (例如 TransactionRepositoryImpl),但它不知道如何建立那些來自外部函式庫的類別 (例如 Room Database) 或介面 (例如 TransactionRepository)。

這時我們需要建立一個 Module,就像是給 Hilt 的一份「製作說明書」。

A. 建立 AppModule.kt 檔案

這個模組將會教 Hilt 如何提供整個 App 都會用到的單例物件,例如資料庫和 DAOs。

Kotlin
import android.app.Application
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class) // 表示這個模組提供的實例將會在整個 App 的生命週期中存活 (單例)
object AppModule {

    @Provides // @Provides 告訴 Hilt,這個函式是用來「提供」一個依賴實例的
    @Singleton // @Singleton 確保這個方法只會被呼叫一次,之後都回傳同一個資料庫實例
    fun provideAppDatabase(app: Application): AppDatabase {
        return Room.databaseBuilder(
            app,
            AppDatabase::class.java,
            "cash_flow_db" // 資料庫檔案名稱
        ).build()
    }

    @Provides
    @Singleton
    fun provideTransactionDao(db: AppDatabase): TransactionDao {
        // Hilt 看到這個函式需要 AppDatabase,
        // 它會自動去尋找上面那個 provideAppDatabase() 方法來取得實例並傳入
        return db.transactionDao()
    }

    @Provides
    @Singleton
    fun provideCategoryDao(db: AppDatabase): CategoryDao {
        return db.categoryDao()
    }

    @Provides
    @Singleton
    fun provideAccountDao(db: AppDatabase): AccountDao {
        return db.accountDao()
    }
}

B. 建立 RepositoryModule.kt 檔案

當我們需要告訴 Hilt,當有人要求一個介面 (TransactionRepository) 時,應該給他哪一個實作類別 (TransactionRepositoryImpl),使用 @Binds 會是更標準且效能更好的做法。

Kotlin
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 // @Binds 用來綁定一個介面和它的實作
    @Singleton
    abstract fun bindTransactionRepository(
        transactionRepositoryImpl: TransactionRepositoryImpl
    ): TransactionRepository // 當有程式碼要求 TransactionRepository 時,Hilt 會提供 TransactionRepositoryImpl 的實例
}

4. 在 ViewModel 中「注入」依賴

萬事俱備!現在我們終於可以在需要的地方「要求」Hilt 提供我們需要的東西了。ViewModel 是最常見的應用場景。

建立 TransactionViewModel.kt

Kotlin
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel // 告訴 Hilt,這個 ViewModel 也需要進行依賴注入
class TransactionViewModel @Inject constructor(
    private val repository: TransactionRepository // 在建構子中宣告你需要什麼
) : ViewModel() {
    
    // Hilt 會自動找到 RepositoryModule,得知要建立一個 TransactionRepositoryImpl,
    // 然後再找到 AppModule,把 TransactionDao 等需要的東西傳給 RepositoryImpl,
    // 最後把建立好的 Repository 實例注入到這個 ViewModel 中。
    // 這一切都是全自動的!

    // 現在你可以在這個 ViewModel 中,直接使用 repository 來存取資料了
    val allTransactions = repository.getAllTransactionDetails()

    // ... 其他的 ViewModel 邏輯 ...
}

總結與下一步

恭喜你!我們已經完成了整個 App 的後台基礎架構搭建,這是一個非常重要的里程碑!

我們回顧一下 Hilt 幫我們做了什麼:

  1. 我們用 @Module@Provides / @Binds 教會了 Hilt 如何建立 Database, DAORepository

  2. 我們在 ViewModel 的建構子前面加上 @Inject,告訴 Hilt:「我需要一個 TransactionRepository」。

  3. Hilt 自動完成了所有繁瑣的物件建立與傳遞工作。

我們的數據流動管道已經完全打通,從最底層的資料庫到最上層的 ViewModel,全部由 Hilt 負責串接。

我們的下一步,終於要進入使用者看得到的部分了!

我們將會建立第一個畫面:顯示交易紀錄列表的主畫面。我們會用到 Jetpack Compose 來宣告式地描繪 UI,並將它與我們剛剛建立好的 TransactionViewModel 連接起來,從 allTransactions 這個 Flow 中讀取數據並顯示在螢幕上。

準備好開始打造你的第一個 Compose 畫面了嗎?