iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Software Development

從 Vibe Coding 到可維護專案:用 Hermes Agent 重讀 RoomRush Android 專題系列 第 20 篇

Day 20 | 管理者選擇「是 / 否」有課之後,課表資料怎麼被改掉?

  • 分享至 

  • xImage
  •  

前言:是 / 否如何變成 X / null,並寫回資料庫?

前一天我們知道了管理者頁面有三條分支可以提供給我們修改,今天要來介紹第一個分支線-LessonManage (課表管理)。

在這一頁中, LessonManageActivity.kt 來選擇「是/否」有課時段是其中蠻核心的邏輯:「是」代表有課,所以寫入 X ;「否」代表沒課,所以寫入 null ,也就是被視為空教室。

而 LessonManageViewModel.kt 是課表管理頁背後的資料狀態中心。今天就來一探究竟這「課表內容編輯」頁面負責了哪些功能。


一開始我認為

一開始設計這頁面時,我們其實沒有想太多,而是在邊寫邏輯和 UI 時,才慢慢把整個頁面可能的樣子呈現出來。

雖然這頁面主要修改的部分是「是否有課時段」以及「刪除教室」,其他選項並沒有可以修改的部分,但以當時的資料格式和技術來看,這是目前想到最好的作法。

而且在修改和刪除之後,其實我們是需要事先和事後再到「所有教室狀態」去確認資料的,這會讓整個操作可能有些繁雜。

https://ithelp.ithome.com.tw/upload/images/20260924/20177899tcRfCrapoX.png


實際讀完後,LessonManage 負責什麼

和 Hermes Agent 一起讀重讀課表管理功能後,LessonManageActivity.kt 是管理者的課表管理頁,讓管理者選擇大樓、樓層、教室、星期、時段與是否有課,可以修改該教室該時段的課表狀態,或刪除整間教室資料。

LessonManageViewModel.kt 是課表管理頁背後的資料狀態中心,負責從 Repository 取得所有課表資料,根據使用者選擇的大樓與樓層產生 Spinner 要顯示的樓層/教室清單,並把 update/delete 交給 Repository。

整套機制有三大技術重點:

1. 用 MediatorLiveData 實現「大樓 ➔ 樓層 ➔ 教室」三級連動

後台選單需要根據使用者挑選的大樓與樓層,動態計算出可選的教室清單。ViewModel 巧妙運用了 MediatorLiveData 來合併多個資料源:

// 只要「全部課表」、「選取大樓」或「選取樓層」任一改變,就自動重算教室清單
val classrooms: LiveData<List<String>> = MediatorLiveData<List<String>>().apply {
addSource(allSchedules) { updateClassrooms() }
addSource(selectedBuilding) { updateClassrooms() }
addSource(selectedFloor) { updateClassrooms() }
}
  • 運作機制: 教室名稱(如 SF304)會先被去掉大樓前綴(SF),再解析開頭數字判定為 3樓。

2. 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" 為可用教室的邏輯吻合。

3. 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 選項

Activity → ViewModel → Repository → DAO → Database

LessonManageActivity
「使用者修改課表」
        ↓
LessonManageViewModel
「整理修改後的課表資料」
        ↓
Repository
「協調資料更新」
        ↓
DAO
「執行資料庫更新」
        ↓
Room Database
「更新教室課表」

我原本以為管理者修改課表只是「選完之後改一個值」,但實際追過程式後才發現,從 Spinner 的選擇,到 ViewModel 篩選出可選教室,再到最後透過 Repository、DAO 寫回 Room Database,中間其實經過了好幾層。


Hermes Agent 幫我檢查出的重點

LessonManageActivity 是 RoomRush 的課表管理頁,從 ManagerActivity 的「課表管理」入口進入。

  • 畫面入口與元件: 由 ManagerActivity 進入,負責管理 lesson_manage.xml 上的 6 組連動選單(大樓、樓層、教室、星期、時段、是否有課)與操作按鈕。
  • 依賴注入: 透過 EmptyRoomFinderApp.appContainer 取得 Repository,注入至 LessonManageViewModel。
  • 雙向資料流動: 將使用者的選單點擊事件寫入 ViewModel;同時 observe 運算後的樓層與教室清單,即時刷新 Spinner。
  • 資料異動與防呆:
    • 修改: 解析選單條件,將「是否有課」映射為 "X" 或 null,透過 copy() 產生新物件後呼叫 viewModel.update()。
    • 刪除: 取得該教室的 Entity 並呼叫 viewModel.delete() 執行實體刪除。

LessonManageViewModel 是課表管理頁背後的資料狀態中心。

  • 資料來源: 透過 repository.getAllSchedules().asLiveData() 監聽全校課表,並持有 selectedBuilding 與 selectedFloor 輸入狀態。
  • 多來源響應計算(MediatorLiveData):
    • floors:監聽「全課表 + 所選大樓」,透過教室名稱規則(如 SF304 去除 SF 取首碼 3)動態計算樓層清單。
    • classrooms:監聽「全課表 + 所選大樓 + 所選樓層」,即時過濾出目標教室清單。
  • 非同步資料寫入: 提供 update() 與 delete() 介面,在 viewModelScope.launch 協程中將異動交由 Repository 寫回 Room 資料庫。

這個檔案帶出的維護觀察

  1. 大樓清單硬編碼:大樓選項直接寫死在程式碼中。未來若要新增其他大樓,或大樓有變更代號,必須手動回原始碼修改並重新打包發布,無法透過資料庫或設定檔動態擴充。
  2. 樓層推算脆弱:樓層完全依賴特定命名規則反推(例如用字串擷取把 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 如何顯示教室名稱,並在點擊時把教室名稱傳給詳細設定頁。


上一篇
Day 19 | 進入後台之後,管理者其實有三條功能線
下一篇
Day 21 | 管理者要修改一間教室,首先要怎麼找到它?(上)
系列文
從 Vibe Coding 到可維護專案:用 Hermes Agent 重讀 RoomRush Android 專題 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言