前一天我們搞懂了資料庫的讀寫與預載機制,今天我們要繼續追資料主線。但這一次視角不太一樣,我們要來聊聊「依賴注入(Dependency Injection)」。
聽起來名詞很深奧,但它要解決的問題其實非常生活化:
前幾天我們看到,不管是查詢頁還是詳細頁的 ViewModel,全部都需要 ClassroomScheduleRepository 來撈資料。但這個 Repository 該由誰建立?又要怎麼交到各個 ViewModel 手上?
於是,專案中出現了 EmptyRoomFinderApp.kt 與 AppContainer.kt,它們就像是整支 App 的「全域共享工具箱」,這就是我們今天要深入的主題。
其實一開始,我不太懂什麼叫做「依賴注入(Dependency Injection)」,所以對今天要討論的這兩個檔案其實很不熟,後來實際了解後,我們首先可以拆解兩個問題:
什麼是「依賴(Dependency)」?
當 A 類別要完成工作,必須用到 B 類別時,B 就是 A 的依賴。
例如:RoomDetailViewModel 想要查課表,必須用到 ClassroomScheduleRepository,這時候 Repository 就是 ViewModel 的依賴。
什麼是「注入(Injection)」?
注入就是 「從外面把東西傳進來給它」,而不是讓它自己動手去做。
想像你今天開了一間早餐店(ViewModel),店裡要賣美式咖啡:
沒有依賴注入(自己做):
早餐店老闆為了賣咖啡,自己去買咖啡豆、買烘焙機、甚至自己蓋了一座咖啡豆烘焙廠。
→ 缺點: 早餐店變得越來越肥大,萬一以後想換成別家供應商的咖啡豆,整間店都得打掉重改。
有依賴注入(外面送進來):
早餐店老闆在請人送咖啡豆(Repository)進來,因此每天早上由總部物流中心(AppContainer)統一配送烘好的咖啡豆進店裡。
→ 優點: 早餐店老闆只需要專心泡咖啡(處理 UI 畫面邏輯),完全不用管咖啡豆是怎麼種出來的。
那為什麼我們需要依賴注入?
主要有以下三個原因:
AppContainer 統一建立一份資料庫與 Repository,所有頁面共用同一個實例,避免記憶體浪費。EmptyRoomFinderApp.kt 與 AppContainer 負責什麼和 Hermes Agent 一起重讀檔案後,EmptyRoomFinderApp.kt 是 RoomRush 自訂的 Application 類別。它是整個 App 啟動時的全域初始化入口,讓其他 Activity 可以透過 AppContainer 取得共用依賴,例如 Repository。
class EmptyRoomFinderApp : Application() {
// AppContainer 實例:用於依賴注入 (Dependency Injection)
// 專案中的其他類別 (如 Activity) 會透過這個實例來獲取所需的依賴 (如 Repository)
lateinit var appContainer: AppContainer
override fun onCreate() {
super.onCreate()
// 在 App 啟動時立即初始化 AppContainer
appContainer = AppContainer(this)
}
}
關鍵細節 1:Application Context 的安全性
這裡傳入 AppContainer(this) 的 this 代表整個應用程式層級的 Context。它從 App 開啟到結束都存在,能確保後續建立資料庫時完全不會造成記憶體洩漏(Memory Leak)。
關鍵細節 2:lateinit 的延遲初始化
因為 Android 的 Application 實例是由系統建立的,無法直接在宣告時拿到 Context,所以透過 lateinit var 宣告,並在系統回呼的 onCreate() 中第一時間完成初始化。
AppContainer.kt 是 RoomRush 的簡單依賴容器。它負責集中建立和保存 App 共用的依賴物件,目前主要是 ClassroomScheduleRepository。
class AppContainer(context: Context) {
// ClassroomScheduleRepository 實例:會被建立一次並在多個頁面間共享
// 利用 "by lazy" (延遲初始化):代表這個 Repository 只有在第一次被存取時才會真正建立
val classroomScheduleRepository: ClassroomScheduleRepository by lazy {
// 1. 取得資料庫實例 (AppDatabase)
val db = AppDatabase.getInstance(context)
// 2. 將資料庫的 DAO 傳入 Repository 並回傳
ClassroomScheduleRepository(db.classroomScheduleDao())
}
}
關鍵細節 1:by lazy 的效能優勢
這段程式碼最重點在於 by lazy。它確保了兩件事:
classroomScheduleRepository 時才建立。關鍵細節 2:清晰的依賴組裝鏈
清楚展示了物件的生成順序:Context → AppDatabase → DAO → Repository
ViewModel 只要向 Container 要 Repository 就好,完全不需要知道背後這些複雜的步驟。
AndroidManifest.xml
註冊 EmptyRoomFinderApp
↓
App 啟動
↓
EmptyRoomFinderApp.onCreate()
↓
建立 AppContainer
↓
Activity
↓
從 AppContainer 取得 Repository
↓
建立 / 取得 ViewModel
↓
將 Repository 提供給 ViewModel
↓
ViewModel 使用 Repository
EmptyRoomFinderApp
↓
建立 AppContainer
↓
AppContainer
↓
使用 Context 建立 / 取得 AppDatabase
↓
從 Database 取得 ClassroomScheduleDao
↓
用 DAO 建立 Repository
↓
Repository
EmptyRoomFinderApp 是 RoomRush 自訂的 Application 類別。
onCreate() 裡建立 AppContainer(this),其中 this 是 Application Context。之後 Activity 可以透過 (application as EmptyRoomFinderApp).appContainer 取得共用依賴,例如 ClassroomScheduleRepository。AppDatabase 的關係不是直接執行 CSV 預載,而是先建立 AppContainer;AppContainer 下一步才會用 Context 建立 Database 和 Repository。AppContainer 是 RoomRush 的簡單依賴容器,由 EmptyRoomFinderApp 在 App 啟動時建立。
classroomScheduleRepository 時,透過 AppDatabase.getInstance(context) 取得資料庫實例,再呼叫 db.classroomScheduleDao() 取得 DAO,最後把 DAO 傳入 ClassroomScheduleRepository。(application as EmptyRoomFinderApp).appContainer.classroomScheduleRepository 取得這個 Repository,並傳給 ViewModelFactory。讀完 EmptyRoomFinderApp 和 AppContainer 後,我把 RoomRush 的資料層建立流程補完整了。
EmptyRoomFinderApp 只負責在 App 啟動時準備共用依賴,這個檔案的重點很短:建立 AppContainer。雖然程式碼只有幾行,但它讓 Activity 可以從全域的 Application 拿到 appContainer,再取得 Repository。這是一種簡單的依賴注入方式,也讓資料層物件不用在每個 Activity 裡重複建立。
而 AppContainer 才是真正建立資料層依賴的地方。它先用 Context 取得 AppDatabase,再從 Database 取得 DAO,最後建立 Repository。這個檔案雖然只有幾行,但它讓我理解「依賴注入」的簡化版本:不是每個 Activity 自己去建立資料庫或 Repository,而是由 AppContainer 統一建立,再提供給需要的地方。這讓資料層物件的來源更集中,也比較容易維護。
各用一句話總結兩個檔案:
EmptyRoomFinderApp是 App 啟動時的全域初始化入口,負責建立AppContainer,讓 Activity / ViewModel 可以取得共用的 Repository。
AppContainer用 Context 取得AppDatabase,再從 Database 取得 DAO,最後建立ClassroomScheduleRepository給 Activity / ViewModel 使用。
了解完 EmptyRoomFinderApp.kt 及 AppContainer.kt 是如何作為全域工具箱之後,我們的資料來源主線就告了一段落。
下一篇,我們要來總結資料來源主線,透過這次的總結,我們可以更看懂 RoomRush 的空教室資料是怎麼來的,並完成第二階段的「資料來源總圖」,看原始的 CSV 檔案,是如何一步步被解析、存入 Room 資料庫、組裝進 Repository,並最終轉化為 ViewModel 中的 emptyRooms。