在第二階段「資料來源主線」,我們在前面介紹到以下這幾個檔案:
AppDatabase.kt:建立 Room Database,讀 CSV 並寫入資料庫。ClassroomSchedule.kt:Room Entity,一筆資料代表一間教室的一週課表。ClassroomScheduleDao.kt:定義資料庫操作。ClassroomScheduleRepository.kt:包裝 DAO,提供資料入口給 ViewModel。AppContainer.kt:建立 Database / DAO / Repository。EmptyRoomFinderApp.kt:建立 AppContainer,讓 Activity 可以取得共用依賴。檔案順序雖然和前幾天文章稍微有點不一樣,但這是方便我們看懂整個資料流程的順序。
透過今天的檔案,希望不論是自己,還是讀者,都能了解 RoomRush 的教室課表資料是如何從 SF.csv/ES.csv 進到 Room Database,再透過 DAO、Repository 提供給 ViewModel 使用。
RoomRush 的初始課表資料是在 SF.csv 以及 ES.csv ,分別對應到聖言樓教室以及進修部大樓教室,兩者分別存放不同的教室課表。
AppDatabase.kt 把 CSV 轉成資料庫資料AppDatabase 建立 Room Database,提供 ClassroomScheduleDao,讀取 SF.csv / ES.csv,用 parseCsv() 依固定欄位順序建立 ClassroomSchedule,合併後用 insertAll() 寫入資料庫。
ClassroomSchedule.kt (Entity)ClassroomSchedule 是 RoomRush 的 Room Entity,對應 classroom_schedules 資料表;每一筆代表一間教室的一週課表;classroom 是主鍵,用來唯一識別教室。
ClassroomScheduleDao.kt (DAO)ClassroomScheduleDao 是資料存取物件,負責定義如何操作 classroom_schedules 資料表;包含: getAll() 用來取得所有課表資料、 insert 單筆新增且衝突時忽略;insertAll 批次新增且衝突時覆蓋;update 更新既有資料;delete 刪除資料、getSchedulesByBuildingAndFloor()、getClassroomDetails()等等。
ClassroomScheduleRepository.kt (Repository)ClassroomScheduleRepository 是負責 ViewModel 和 DAO 中間的資料層中介,提供 getAllSchedules()、insert()、insertAll()、update()、delete()、findByName()、getClassroomDetails() 等在 DAO 用到的方法給 ViewModel 使用。
EmptyRoomFinderApp → AppContainerEmptyRoomFinderApp 建立 AppContainer → AppContainer 用 Context 取得 Database → 從 Database 取得 DAO → 用 DAO 建立 Repository → Activity 從 Container 取得 Repository;Activity 通常會把 Repository 傳給 ViewModelFactory,再建立 ViewModel。
QueryResultViewModel 產出 emptyRoomsQueryResultViewModel 是透過 repository.getAllSchedules() 取得所有課表。
再通過三個篩選條件才會被放進 emptyRooms 供畫面顯示:
building)符合(如選取聖言樓 ➔ 教室開頭為 SF)floor)符合(如選取 3 樓 ➔ 教室編號包含 3)day / time)可用(精準判定條件為該節次欄位代碼 != "X")查詢邏輯重複與分散:
DAO 和 Repository 裡原本就寫了 queryEmptyRooms 的篩選邏輯,但目前主要查詢頁的空教室篩選邏輯是在 QueryResultViewModel ,造成邏輯分散兩處。
每次開啟的資料覆蓋風險:
DatabaseCallback 的 onOpen 每次都會觸發讀取 CSV 並執行 insertAll(REPLACE);代表每次資料庫打開時可能重新讀 CSV 並 insertAll,管理功能修改過資料可能被 CSV 覆蓋;如果未來 App 允許使用者手動修改或自訂教室資訊,這些自訂資料在 App 重開時會直接被 CSV 暴力覆蓋還原。
CSV 欄位順序依賴:
parseCsv() 內部是硬編碼(Hardcoded)依照索引順序(parts[0], parts[1]...)來讀取 47 個欄位,只要 CSV 欄位順序稍有微調,整個解析可能就會錯位。
catch 吞掉錯誤:
CSV 解析過程中的 try-catch 在發生例外時直接回傳 emptyList,雖然防止了 Crash,卻也讓開發者在發生錯誤時難以發現哪裡有錯。
SF.csv / ES.csv
↓
assets 中的初始課表資料
↓
AppDatabase
↓
讀取 CSV 並解析資料
↓
ClassroomSchedule
↓
一筆資料代表一間教室的一週課表
↓
ClassroomScheduleDao
↓
執行資料庫操作
↓
Room Database
↓
儲存 ClassroomSchedule 資料
↓
ClassroomScheduleRepository
↓
統整資料存取
↓
AppContainer
↓
建立並提供 Database、DAO、Repository
↓
EmptyRoomFinderApp
↓
提供 AppContainer
↓
Activity / ViewModelFactory
↓
取得 Repository 並傳給 ViewModel
↓
QueryResultViewModel
↓
根據大樓、樓層、星期、時段篩選資料
↓
emptyRooms
↓
得到符合條件的空教室
RoomRush 的初始課表資料存放在 assets 裡的 SF.csv 和 ES.csv,分別對應聖言樓與進修部大樓。
AppDatabase 在資料庫建立或開啟時讀取 CSV,透過 parseCsv() 略過表頭、依固定欄位順序解析每一列,建立 ClassroomSchedule 物件,最後用 dao.insertAll() 寫入 Room Database。ClassroomSchedule 是 Entity,一筆代表一間教室的一週課表。ClassroomScheduleDao 定義資料表操作;ClassroomScheduleRepository 包裝 DAO 給 ViewModel 使用。EmptyRoomFinderApp 建立 AppContainer;AppContainer 用 Context 取得 Database、DAO 並建立 Repository。QueryResultViewModel 透過 repository.getAllSchedules() 取得所有課表,根據大樓、樓層、星期與時段篩出 emptyRooms。| 階段 | 檔案 / 資料 | 負責什麼 | 產生 / 傳遞什麼 |
|---|---|---|---|
| 1 | SF.csv / ES.csv |
初始課表資料 | CSV row |
| 2 | AppDatabase.kt |
建立資料庫、讀取 CSV、預載資料 | ClassroomSchedule list |
| 3 | ClassroomSchedule.kt |
定義 Entity / 資料表形狀 | classroom_schedules row |
| 4 | ClassroomScheduleDao.kt |
定義資料庫操作 | Flow / CRUD / 查詢方法 |
| 5 | ClassroomScheduleRepository.kt |
包裝 DAO,提供給 ViewModel 使用 | Repository API |
| 6 | AppContainer.kt |
建立 Database / DAO / Repository | classroomScheduleRepository |
| 7 | EmptyRoomFinderApp.kt |
建立並保存 AppContainer |
appContainer |
| 8 | QueryResultViewModel.kt |
使用 Repository 資料篩選空教室 | emptyRooms |
這次整理資料來源主線後,我終於看懂 RoomRush 的空教室資料是怎麼來的。
它不是直接寫在 ViewModel 裡,也不是使用者臨時輸入,而是從 assets 裡的 SF.csv 和 ES.csv 開始。AppDatabase 會讀取這兩份 CSV,把每一列轉成 ClassroomSchedule,再寫進 Room Database。
接著,DAO 負責定義資料表操作,Repository 則把 DAO 包裝成 ViewModel 可以使用的方法。
最後,EmptyRoomFinderApp 和 AppContainer 負責建立並提供 Repository,讓 QueryResultViewModel 可以取得所有課表資料並篩選出空教室。
這條資料流也讓我看到幾個技術債:CSV 匯入可能覆蓋資料庫修改、CSV 欄位順序高度依賴程式碼、錯誤處理會把例外吞掉,以及空教室查詢邏輯分散在 Repository 和 ViewModel。
這些問題不一定影響目前展示,但如果要把專案整理得更穩定,就需要被記錄與改善。
今天,我們已經把這條資料來源主線拆開看過:知道 CSV 如何被解析成 ClassroomSchedule、DAO 如何操作資料、Repository 如何提供給 ViewModel,以及最後如何被查詢頁使用。
但在接續到了管理者功能前,我想反過來看另一件事:如果這些資料不是只有被查詢,而是還會被管理者修改呢?
因此下一篇,我們會先來探究 AppDatabase.kt 提到的:onOpen 每次開啟資料庫都會預載 CSV,如果管理者曾修改資料,可能被 CSV 覆蓋。