前一天我們知道了管理者頁面有三條分支可以提供給我們修改,今天要來介紹第一個分支線-LessonManage (課表管理)。
在這一頁中, LessonManageActivity.kt 來選擇「是/否」有課時段是其中蠻核心的邏輯:「是」代表有課,所以寫入 X ;「否」代表沒課,所以寫入 null ,也就是被視為空教室。
而 LessonManageViewModel.kt 是課表管理頁背後的資料狀態中心。今天就來一探究竟這「課表內容編輯」頁面負責了哪些功能。
一開始設計這頁面時,我們其實沒有想太多,而是在邊寫邏輯和 UI 時,才慢慢把整個頁面可能的樣子呈現出來。
雖然這頁面主要修改的部分是「是否有課時段」以及「刪除教室」,其他選項並沒有可以修改的部分,但以當時的資料格式和技術來看,這是目前想到最好的作法。
而且在修改和刪除之後,其實我們是需要事先和事後再到「所有教室狀態」去確認資料的,這會讓整個操作可能有些繁雜。

LessonManage 負責什麼和 Hermes Agent 一起讀重讀課表管理功能後,LessonManageActivity.kt 是管理者的課表管理頁,讓管理者選擇大樓、樓層、教室、星期、時段與是否有課,可以修改該教室該時段的課表狀態,或刪除整間教室資料。
LessonManageViewModel.kt 是課表管理頁背後的資料狀態中心,負責從 Repository 取得所有課表資料,根據使用者選擇的大樓與樓層產生 Spinner 要顯示的樓層/教室清單,並把 update/delete 交給 Repository。
整套機制有三大技術重點:
MediatorLiveData 實現「大樓 ➔ 樓層 ➔ 教室」三級連動後台選單需要根據使用者挑選的大樓與樓層,動態計算出可選的教室清單。ViewModel 巧妙運用了 MediatorLiveData 來合併多個資料源:
// 只要「全部課表」、「選取大樓」或「選取樓層」任一改變,就自動重算教室清單
val classrooms: LiveData<List<String>> = MediatorLiveData<List<String>>().apply {
addSource(allSchedules) { updateClassrooms() }
addSource(selectedBuilding) { updateClassrooms() }
addSource(selectedFloor) { updateClassrooms() }
}
SF304)會先被去掉大樓前綴(SF),再解析開頭數字判定為 3樓。X / null 的空堂語意val hasClass = hasClassSpinner.selectedItem.toString() == "是"
val newValue = if (hasClass) "X" else null // 有課設為 "X",沒課設為 null
val updatedSchedule = getUpdatedSchedule(scheduleToUpdate, dayKey, timeKey, newValue)
viewModel.update(updatedSchedule)
"X",空堂則寫入 null 。這和前台查詢頁(QueryResultViewModel )判斷 != "X" 為可用教室的邏輯吻合。viewModelScope 串接資料流fun update(schedule: ClassroomSchedule) = viewModelScope.launch {
repository.update(schedule)
}
fun delete(schedule: ClassroomSchedule) = viewModelScope.launch {
repository.delete(schedule)
}
repository.update() 寫入 Room 資料庫後,由於一開始的 allSchedules 是透過 Flow.asLiveData() 監聽,資料庫的異動會自動通知 ViewModel 更新清單,進而驅動 UI 刷新,不需手動。LessonManageActivity.kt:管理者修改課表的流程完整操作流程:
ManagerActivity
「進入課表管理」
↓
LessonManageActivity
↓
選擇大樓
↓
選擇樓層
↓
選擇教室
↓
選擇星期
↓
選擇時段
↓
選擇「有課 / 無課」
↓
建立要修改的課表資料
「有課 → X」
「無課 → null」
↓
ViewModel.update()
↓
Repository.update()
↓
DAO.update()
↓
Room Database
「更新教室課表」
LessonManageViewModel.kt:資料如何提供給 Activity讀取 → 篩選 → UI 更新:
Repository.getAllSchedules()
↓
取得所有教室課表
↓
LessonManageViewModel
↓
根據使用者目前的選擇
│
├── 選擇大樓
│ ↓
│ 計算可選樓層
│ ↓
│ floors
│
└── 選擇樓層
↓
計算可選教室
↓
classrooms
↓
Activity observe
↓
更新 Spinner 選項
LessonManageActivity
「使用者修改課表」
↓
LessonManageViewModel
「整理修改後的課表資料」
↓
Repository
「協調資料更新」
↓
DAO
「執行資料庫更新」
↓
Room Database
「更新教室課表」
我原本以為管理者修改課表只是「選完之後改一個值」,但實際追過程式後才發現,從 Spinner 的選擇,到 ViewModel 篩選出可選教室,再到最後透過 Repository、DAO 寫回 Room Database,中間其實經過了好幾層。
LessonManageActivity 是 RoomRush 的課表管理頁,從 ManagerActivity 的「課表管理」入口進入。
ManagerActivity 進入,負責管理 lesson_manage.xml 上的 6 組連動選單(大樓、樓層、教室、星期、時段、是否有課)與操作按鈕。EmptyRoomFinderApp.appContainer 取得 Repository,注入至 LessonManageViewModel。observe 運算後的樓層與教室清單,即時刷新 Spinner。"X" 或 null,透過 copy() 產生新物件後呼叫 viewModel.update()。viewModel.delete() 執行實體刪除。LessonManageViewModel 是課表管理頁背後的資料狀態中心。
repository.getAllSchedules().asLiveData() 監聽全校課表,並持有 selectedBuilding 與 selectedFloor 輸入狀態。MediatorLiveData):
floors:監聽「全課表 + 所選大樓」,透過教室名稱規則(如 SF304 去除 SF 取首碼 3)動態計算樓層清單。classrooms:監聽「全課表 + 所選大樓 + 所選樓層」,即時過濾出目標教室清單。update() 與 delete() 介面,在 viewModelScope.launch 協程中將異動交由 Repository 寫回 Room 資料庫。SF304 的 3 當作三樓)。一旦遇到地下室(如 B1)、特殊命名教室或雙位數高樓層(如 10 樓),正則解析與字串邏輯就會直接失靈。讀到 LessonManageActivity 時,我第一次看到管理者功能真的開始修改資料。前面的 ManagerActivity 只是選單頁,但這一頁會根據管理者選擇的大樓、樓層、教室、星期與時段,修改某一格課表欄位,甚至可以刪除整間教室資料。
這頁最重要的資料邏輯是「是否有課」的轉換:選擇「是」會寫入 X,代表有課 / 被佔用;選擇「否」會寫入 null,代表沒有課。
這剛好接回查詢頁的邏輯:QueryResultViewModel 判斷不是 X 的時段為可用,所以管理者把某時段改成 null 後,該教室在查詢頁就可能變成空教室。
同時,這也讓前面看到的技術債變得更明確。因為 LessonManageActivity 真的會 update / delete Room Database,但 AppDatabase 在 onOpen 時會重新讀取 CSV 並 insertAll。
如果 CSV 還保留舊資料,管理者的修改或刪除就可能在下次開啟資料庫時被覆蓋或恢復。這是後續整理專案時很值得記錄的問題。
接著看 Activity 背後的 LessonManageViewModel。這個 ViewModel 的角色很清楚:Activity 負責顯示 Spinner 和按鈕,ViewModel 則負責計算 Spinner 背後的資料。
它從 Repository 取得所有課表資料,再根據目前選到的大樓與樓層,算出樓層清單與教室清單。
這裡我第一次比較清楚看到「三級連動」是怎麼做的:
當大樓改變時,ViewModel 會根據教室名稱前綴 SF 或 ES 推算樓層;當樓層改變時,ViewModel 再篩出該樓層的教室名稱。這些結果透過 LiveData 給 Activity observe,Activity 再更新 Spinner。
各一句話重點總結:
LessonManageActivity讓管理者選擇教室、星期、時段與是否有課,並透過 ViewModel / Repository 修改或刪除 Room Database 裡的課表資料。
LessonManageViewModel把所有課表資料轉成大樓、樓層、教室三級連動清單,並把修改 / 刪除交給 Repository 執行。
了解完課表內容編輯這條線後,我們下一個要介紹到管理者功能的第二條分支「教室細項設定」,由於這條線的設計比較豐富,因此我們會分成上下兩個部分來說明。
下一篇是教室詳細設定頁,我們會首先會先讀 DetailChooseActivity.kt 以及 ClassroomAdapter.kt ,理解教室詳細設定流程如何先列出所有教室,並把選到的教室名稱傳給下一頁;教室列表 Adapter 如何顯示教室名稱,並在點擊時把教室名稱傳給詳細設定頁。