前一天,我們完成了選擇教室的這部分邏輯,今天一樣是繼續深入「教室細項設定」,但會著重在資料的更新。
當我們點擊了列表中的某一間教室時,會進入到這教室的詳細內容,裡面會顯示該教室是什麼類型和對應到是否可飲食的邏輯,管理者有權限可以更改其教室類型。
DetailSetupActivity.kt 會先接收上一頁(ClassroomAdapter.kt)傳來的 CLASSROOM_NAME ,教室類型會根據資料庫所設定的英文名稱,轉換成對應的中文顯示。
一開始設計這個頁面時,有想過大概是怎麼樣的呈現方式,不過實際做完這個 UI 後,發現其實能互動的邏輯很少。
因為每當點擊列表中某個教室時,只能修改該教室的教室名稱而已,我們把是否可飲食的選項,設定成由教室類型自動決定,這樣寫可能少了一點彈性修改空間,但也是為了確保資料的一致性。
| UI 畫面 | 操作流程 |
|---|---|
![]() |
![]() |
DetailSetupActivity.kt 負責什麼和 Hermes Agent 一起重讀教室詳細設定功能後,我了解了 DetailSetupActivity.kt 是教室詳細設定流程中,真正修改單一教室詳細資料的頁面。
它會接收上一頁傳來的 CLASSROOM_NAME,該頁面可以直接看到該教室資料,也會顯示教室名稱;主要可以查詢到的詳細內容是教室類型與是否可飲食,並讓管理者修改教室類型後儲存到 Room Database。
核心功能的三大技術重點:
SavedStateHandle 安全接收參數與觀察教室狀況// 1. Factory 傳入 intent.extras,讓 ViewModel 透過 SavedStateHandle 取值
private val viewModel: ClassroomManageViewModel by viewModels {
ClassroomManageViewModelFactory(
(application as EmptyRoomFinderApp).appContainer.classroomScheduleRepository,
this,
intent.extras
)
}
// 2. 觀察教室資料,查無資料時防呆退出
viewModel.classroomSchedule.observe(this) { schedule ->
if (schedule != null) {
roomNameTextView.text = schedule.classroom
setupSpinners(schedule)
} else {
Toast.makeText(this, "錯誤:找不到教室資料", Toast.LENGTH_LONG).show()
finish()
}
}
SavedStateHandle 取得上一頁 Intent 傳來的 CLASSROOM_NAME。// 1. 資料庫英文代碼 <-> UI 中文顯示
private fun mapClassroomTypeToDisplay(type: String?): String {
return when (type) {
"Normal" -> "一般教室"
"Computer" -> "電腦教室"
"Drawing" -> "製圖教室"
"Lab" -> "實驗室"
"MeetingRoom" -> "研討室"
else -> "一般教室"
}
}
// 2. 是否可飲食是由 classroomType 動態判定
val foodAllowedPosition = if (schedule.classroomType == "Normal") 0 else 1
foodAllowedSpinner.setSelection(foodAllowedPosition)
foodAllowedSpinner.isEnabled = false
classroomType == "Normal" 推導而來。一般教室顯示「是」,其他類型顯示「否」,且使用者不能直接修改。fun updateClassroomType(newType: String) = viewModelScope.launch {
val currentSchedule = classroomSchedule.value ?: return@launch
// 透過 copy 只替換 classroomType 欄位,保留其他所有課表時段資料
val updatedSchedule = currentSchedule.copy(classroomType = newType)
repository.update(updatedSchedule)
_updateResult.postValue(true)
}
copy(classroomType = newType) 產生更新後的物件,呼叫 Repository 更新資料庫,再顯示更新成功。管理者點選教室
↓
ClassroomManageActivity
↓
取得教室名稱 CLASSROOM_NAME
↓
SavedStateHandle
「保存並提供教室名稱」
↓
ViewModel
「向 Repository 請求教室資料」
↓
Repository
「查詢指定教室」
↓
ClassroomSchedule
「取得該教室的課表資料」
↓
Activity
「顯示教室資料與類型」
管理者修改教室類型
↓
ClassroomManageActivity
「取得修改後的類型」
↓
ViewModel
「處理資料更新」
↓
Repository.update()
「傳遞更新請求」
↓
DAO.update()
「執行資料庫更新」
↓
Room Database
「儲存修改後的教室資料」
DetailSetupActivity是教室詳細設定流程中真正修改單一教室詳細資料的頁面。它實際 class 名稱是 ClassroomManageActivity,layout 是 detail_setup.xml。
ClassroomAdapter 會透過 Intent 傳入 CLASSROOM_NAME,本頁的 ViewModel 透過 SavedStateHandle 取得這個教室名稱,並用 repository.getClassroomDetails(classroomName).asLiveData() 查詢該教室資料。setupSpinners() 設定教室類型與是否可飲食。viewModel.updateClassroomType(newType),最後透過 repository.update() 更新 Room Database 中的 classroomType 欄位。DetailSetupActivity.kt 之後,這裡的 DetailChooseActivity.kt 同樣存在「檔名與內部 Class 名稱(ClassroomChooseManageActivity)對不上」的狀況。雖然不影響編譯,但在 IDE 進行全域搜尋或專案重構時,容易造成混亂。讀到 DetailSetupActivity時,我看到了教室詳細設定流程真正修改資料的地方。
前一頁只是列出所有教室並傳出 CLASSROOM_NAME,這一頁則根據這個教室名稱查詢資料,顯示教室類型與是否可飲食,並讓管理者修改教室類型。
這裡的設計有一個值得注意的地方:畫面上雖然有「是否可飲食」,但它不是獨立欄位,而是由 classroomType 推導出來。只要教室類型是 Normal,就顯示可飲食;其他類型則顯示不可飲食。這種做法簡單,但未來如果需要更細緻的規則,例如某間一般教室不能飲食,就會遇到資料模型限制。
另外,這個檔案也延續了命名不一致的問題:檔案叫 DetailSetupActivity.kt,class 叫 ClassroomManageActivity,layout 叫 detail_setup.xml。這不影響執行,但對閱讀者來說會增加對照成本。
一句話總結這檔案:
ClassroomManageActivity接收CLASSROOM_NAME,查出該教室資料,讓管理者修改classroomType,並透過Repository.update()寫回資料庫。
結束了「教室設定」這第二條管理者功能後,下一篇我們會進到最後最後一條支線-「教室狀態」。
下一個檔案我們會先讀 AllClassroomsActivity.kt ,從管理者主頁點擊「所有教室狀態」按鈕後,我們會看到所有教室狀態總覽頁如何顯示教室資訊,這畫面也提供了新增教室功能。