iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Software Development

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

Day 25 | 完整課表頁為什麼顯示的是有課時段,而不是空教室?

  • 分享至 

  • xImage
  •  

前言:為什麼這頁列出的是有課時段?它和查詢空教室頁有什麼差異?

上一篇我們拆解了所有教室狀態清單的卡片設計,今天我們要跟隨點擊事件,深入「單一教室完整課表」頁面-AllClassroomsSchedule.kt。

點進這頁時,很多人直覺會想:「既然這是一款找空教室的 App,為什麼這裡列出的反而是『有課時段』?」這背後其實是基於 資訊密度與閱讀體驗(UX) 的考量:

  • 前台查詢頁(以『空』為導向): 學生的目的是「當下找空位」,因此系統過濾掉佔用時段,直接給出可用的空教室清單。
  • 管理者課表詳情(以『忙碌』為標記): 一間教室一週有 40~50 個節次,若把所有「空堂」全列出來,畫面會過度冗長繁雜。反之,將每週少數被佔用的時段精準列出(如圖中星期二的第 5~7 節),並在沒有課的日子標記「(此日無課)」,管理者就能用最低的視覺負擔,一眼掌握整間教室的每週佔用狀態!

https://ithelp.ithome.com.tw/upload/images/20261003/20177899ZseoUg0Kg2.png


一開始我認為

最初規劃這頁面時,我曾設想是否該把每個佔用時段的「課程名稱」(例如:作業系統、計算機概論)一併顯示出來,讓資訊看起來更豐富。

但在與組員深入討論後,我們決定捨棄課程名稱,回歸產品的核心本質:

  • 聚焦核心價值(Find Empty Rooms): 這款 App 的本質是「空教室查詢系統」,使用者最關心的是「這個時段有沒有人借用」,而不是「這堂課是什麼名稱」。
  • 保持資訊純粹,避免過載: 前台查詢演算法只需判定欄位是否為佔用狀態("X")。若在後台強行塞入課程名稱,不僅增加資料結構與 CSV 解析的複雜度,對只想確認空間空檔的管理者而言更是多餘的資訊噪音。

這項團隊討論讓我們確立了「 如無必要,勿增實體 」的原則——在產品設計上,精準滿足核心需求,這遠比堆砌看似華麗卻無關緊要的資訊更為重要。


實際讀完後,AllClassroomsSchedule.kt 負責什麼

和 Hermes Agent 一起重讀 AllClassroomsSchedule.kt 後,它是管理者從「所有教室狀態總覽」點進某一間教室後,查看該教室完整課表的頁面。

這檔案可以分成以下三大實作重點:

1. 響應式資料流(SavedStateHandle + flatMapLatest)

// 1. 從 SavedStateHandle 取得傳入的 CLASSROOM_NAME
private val classroomName: LiveData<String?> =
    savedStateHandle.getLiveData(ClassroomScheduleDetailActivity.EXTRA_CLASSROOM_NAME)

// 2. 當教室名稱變更時,自動切換查詢對應的課表資料流
val classroomSchedule: LiveData<ClassroomSchedule?> = classroomName.asFlow().flatMapLatest { name ->
    if (name != null) repository.getClassroomDetails(name) else flowOf(null)
}.asLiveData()
  • 技術細節:使用 flatMapLatest 能保證當教室名稱改變時,前一次未完成的資料庫查詢會被自動取消並切換為最新的查詢,最後轉為 LiveData 提供 UI 監聽。

2. 格式化課表

// 僅針對值為 "X"(佔用)的時段進行排版輸出
if (getStatusViaReflection(schedule, "$dayKey$p") == "X") {
		val timeRange = timeMap[p] ?: ""
		val periodName = if (p == "N") "第 N 節" else "第 $p 節"
		sb.append("$periodName $timeRange\n")
		hasClass = true
}
// 若該天所有節次都不是 "X",則輸出兜底提示
if (!hasClass) {
		sb.append("(此日無課)\n")
}
  • 邏輯對齊: X 代表有課。程式碼逐節掃描,只把被佔用的時段與節次(如 13:40 - 14:30)拼裝出來,遇到整天沒課則補上 (此日無課),確保版面乾淨。

3. 反射取得欄位

private fun getStatusViaReflection(schedule: ClassroomSchedule, fieldName: String): String? {
    return try {
        val field = schedule::class.java.getDeclaredField(fieldName)
        field.isAccessible = true
        field.get(schedule) as? String
    } catch (e: Exception) {
        null
    }
}
  • 技術優勢:ClassroomSchedule 擁有超過 45 個時段屬性。若不使用反射,就必須寫出一個包含 45 條分支的巨大 when(fieldName) 判斷式。反射大幅減少了重複的程式碼。
  • 技術債:欄位名稱錯誤時編譯器不會提前提醒,重構風險較高。

它接在哪一條流程上

AllClassroomsSchedule.kt 承接 AllClassroomsAdapter 中點擊教室卡片後:

AllClassroomsAdapter
「點擊教室卡片」
        │
        ▼
Intent
「取得 CLASSROOM_NAME」
        │
        ▼
SavedStateHandle
「保存並提供教室名稱」
        │
        ▼
ViewModel
「向 Repository 請求課表資料」
        │
        ▼
Repository
「查詢指定教室的資料」
        │
        ▼
ClassroomSchedule
「取得該教室的一週課表」
        │
        ▼
AllClassroomsSchedule.kt
「整理並格式化課表時段」
        │
        ▼
判斷各時段的資料
「X 代表有課」
        │
        ▼
顯示完整課表
「呈現該教室一週的有課時段」

Hermes Agent 幫我檢查出的重點

AllClassroomsSchedule.kt 是從所有教室總覽頁點進單一教室後,顯示該教室完整課表的頁面。它的實際 class 名稱是 ClassroomScheduleDetailActivity,layout 是 activity_classroom_schedule_detail.xml。

前一頁 AllClassroomsAdapter 會透過 Intent 傳入 CLASSROOM_NAME,本頁的 ViewModel 透過 SavedStateHandle 取得這個名稱,再用 repository.getClassroomDetails(name) 查詢該教室完整資料。

Activity observe 到 ClassroomSchedule 後,會顯示「教室名稱 課表」,並呼叫 formatScheduleToDisplayString() 把一週課表轉成文字。這個函式會掃描星期一到星期五、每一天第 1、2、3、4、N、5、6、7、8 節,只把欄位值等於 X 的時段列出來,因為 X 代表有課 / 被佔用。


這個檔案帶出的維護觀察

  1. 命名不一致:和前面提到的問題一樣,檔名和 class 名稱不一樣,讀專案時會需要特別對照。
  2. 「反射」動態撈取欄位,喪失編譯期檢查:反射動態取得 mon1、tue2 這類欄位,但如果名稱打錯,編譯器不會提前抓到、重構欄位名稱時很危險。
  3. 程式邏輯硬編碼:目前時間表、星期與節次都是硬編碼,如果需要改變課程規則,會需要再到程式碼修改邏輯或條件。

小結

讀到 AllClassroomsSchedule.kt 時,我看到「所有教室狀態總覽」的最後一段:管理者點進某一間教室後,系統會用 CLASSROOM_NAME 查詢該教室完整課表,並把有課時段整理成文字顯示。

這一頁特別適合再次確認 X 的意義。前面查詢空教室時,我們是用「不是 X」判斷可用;但這一頁標題是「有課時段」,所以它只列出等於 X 的欄位。也就是說,同一份課表資料在不同頁面有不同用途:查詢頁找空閒,詳細頁列佔用。

這個檔案也有幾個值得注意的維護性問題。首先,檔案名稱、class 名稱、layout 名稱不一致,讀專案時需要對照。其次,程式用反射動態取得 mon1、tue2 這類欄位,雖然避免寫很多 when,但欄位名稱寫錯時編譯器不會提前提醒。最後,上課時間、星期與節次都硬編碼在程式裡,如果學校時間表或課程規則改變,就需要修改程式碼。

一句話重點總結:

ClassroomScheduleDetailActivity 接收 CLASSROOM_NAME,查出該教室完整課表,並把 X 代表的有課時段格式化顯示。


下一篇預告:管理者功能總結:這個後台到底怎麼維護教室資料?

完成了的管理者功能的最後一部分-「所有教室狀態」後,我們的管理者線也跟著告了一段落。

接下來我們會總結管理者功能,從登入開始,進入後台後分成三條主要功能線:「課表管理」、「教室設定」、「教室狀態」,這些主線是怎麼相輔相成的?下一篇會帶大家看清這三條主線如何各司其職、相互協同,共同撐起整套教室資料維護。


上一篇
Day 24 | 一張教室卡片背後,藏著完整課表的入口
下一篇
Day 26 | 管理者功能總結:這個後台到底怎麼維護教室資料?
系列文
從 Vibe Coding 到可維護專案:用 Hermes Agent 重讀 RoomRush Android 專題 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言