本程式碼研究室是 Android Kotlin 基礎課程的一部分。使用程式碼研究室逐步完成程式碼課程後,您將能充分發揮本課程的潛能。所有課程程式碼研究室清單均列於 Android Kotlin 基礎程式碼研究室到達網頁。
簡介
這個程式碼研究室將示範如何使用 ViewModel
和片段來導入導覽功能。請記住,目標是將 when 邏輯加到 ViewModel
,但要定義片段和導覽檔案中的路徑。為了達成這個目標,您可以使用檢視模型、片段、LiveData
和觀察器。
程式碼研究室的顯示,是以簡潔的方式追蹤基本程式碼的按鈕狀態。這樣一來,每個按鈕只會在使用者輕觸按鈕時才啟用及點擊。
須知事項
您應該很熟悉:
- 運用活動、片段和視圖來建構基本的使用者介面 (UI)。
- 在片段之間瀏覽,並使用
safeArgs
在片段之間傳送資料。 - 查看模型、查看模型工廠、轉換,以及
LiveData
及其觀測器。 - 如何建立
Room
資料庫、建立資料存取物件 (DAO) 以及定義實體。 - 如何使用協同程式處理資料庫互動和其他長時間執行的工作。
課程內容
- 如何更新資料庫中現有的睡眠品質記錄。
- 如何使用
LiveData
追蹤按鈕狀態。 - 如何在活動中顯示 Snackbar。
執行步驟
- 擴充 TrackMySleepquality 應用程式以收集品質評分、將評分新增到資料庫,並顯示結果。
- 使用
LiveData
觸發 Snackbar 的顯示。 - 使用
LiveData
即可啟用及停用按鈕。
在這個程式碼研究室中,您需要為「MyMySleepquality」應用程式建立睡眠品質的錄音和敲定的使用者介面。
這個應用程式有兩個畫面,以片段表示,如下圖所示。
第一個畫面 (如左側所示) 為開始和停止追蹤的按鈕。畫面會顯示使用者的睡眠資料。[清除] 按鈕永久刪除應用程式已收集的所有資料。
第二個螢幕則是根據選取睡眠品質評分的方式。在應用程式中,評分是以數字表示。在開發方面,應用程式會同時顯示臉部圖示和對應的數值。
使用者流程如下:
- 使用者開啟應用程式並顯示睡眠追蹤畫面。
- 使用者輕觸 [開始] 按鈕。這會記錄開始時間,並顯示。[開始] 按鈕已停用,[停止] 按鈕也已啟用。
- 輕觸 [停止] 按鈕。這會記錄結束時間,並開啟睡眠品質畫面。
- 使用者選取睡眠品質圖示。螢幕會關閉,追蹤畫面會顯示睡眠的結束時間和睡眠品質。[停用] 按鈕已停用,且 [開始] 按鈕已啟用。應用程式已可繼續進行其他夜晚,
- 如果資料庫中有資料,[清除] 按鈕就會啟用。使用者輕觸 [清除] 按鈕時,系統會將他們的所有資料清除,而不會經過清除,不會出現「你同意升級」的訊息。
此應用程式使用簡易架構,如下圖所示,在完整架構的情境中。應用程式僅使用下列元件:
- UI 控制器
- 檢視模型及
LiveData
- 會議室資料庫
本程式碼研究室假設您已知道如何使用片段和導覽檔案進行瀏覽。為節省您的工作時間,我們會妥善提供這組代碼。
步驟 1:檢查程式碼
- 如要開始,請在上個程式碼研究室結尾使用您自己的程式碼,或下載範例程式碼。
- 在範例程式碼中,檢查
SleepQualityFragment
。此類別會翻轉版面配置、取得應用程式並傳回binding.root
。 - 在設計編輯器中開啟 navigation.xml。我們發現從
SleepTrackerFragment
到SleepQualityFragment
有一項導航路徑,從SleepQualityFragment
到SleepTrackerFragment
又回到了一次。 - 檢查 navigation.xml 的程式碼。請特別注意,尋找名稱為
sleepNightKey
的<argument>
。
當使用者從SleepTrackerFragment
進入SleepQualityFragment,
應用程式後,系統會將sleepNightKey
傳送到SleepQualityFragment
,讓需要更新的夜晚睡覺。
步驟 2:新增睡眠品質追蹤功能的導覽
導覽圖中已經包含從 SleepTrackerFragment
到 SleepQualityFragment
以及再次檢視的路徑。不過,將實作從特定片段移到下一個片段的點擊處理常式尚未編寫程式碼。您現在已經將這段程式碼加到 ViewModel
中。
在點擊處理常式中,您可以設定 LiveData
,讓應用程式瀏覽至其他目的地時變更。片段會觀察這個 LiveData
。資料變更時,片段會瀏覽至目的地,並通知檢視模型已完成該作業,並重設狀態變數。
- 開啟
SleepTrackerViewModel
。您必須新增導覽功能,當使用者輕觸 [停止] 按鈕時,應用程式就會瀏覽至SleepQualityFragment
以收集品質評分。 - 在
SleepTrackerViewModel
中建立一個LiveData
,讓應用程式回到SleepQualityFragment
的時間點。使用封裝將LiveData
的可獲取版本只暴露在ViewModel
上。
您可以將這段程式碼置於課程內文頂層。
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
- 新增會重設導覽變數的
doneNavigating()
函式。
fun doneNavigating() {
_navigateToSleepQuality.value = null
}
- 在停止按鈕
onStopTracking()
的 click click 中,觸見到SleepQualityFragment
。請將函式結尾的 _navigateToSleepQuality
變數設為launch{}
區塊內的最後一項。請注意,這個變數已設為night
。當這個變數有值時,應用程式會瀏覽至SleepQualityFragment
,並直到晚上顯示
。
_navigateToSleepQuality.value = oldNight
SleepTrackerFragment
需要觀察 _navigateToSleepQuality
,才能讓應用程式知道何時要導航。在SleepTrackerFragment
中,為onCreateView()
新增navigateToSleepQuality()
的觀測器。請注意,此次匯入作業並不清楚,您必須匯入androidx.lifecycle.Observer
。
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})
- 在觀察器區塊內,瀏覽並沿著目前夜晚的 ID 進行瀏覽,然後呼叫
doneNavigating()
。如果您的匯入作業不明確,請匯入androidx.navigation.fragment.findNavController
。
night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
sleepTrackerViewModel.doneNavigating()
}
- 建構並執行應用程式。依序輕觸 [開始] 和 [停止],系統隨即會將您導向
SleepQualityFragment
畫面。如要返回,請使用系統的 [返回] 按鈕。
在這項工作中,您會記錄睡眠品質,並回到睡眠追蹤器片段。螢幕應自動更新,向使用者顯示更新的值。您必須建立 ViewModel
和 ViewModelFactory
,且您需要更新 SleepQualityFragment
。
步驟 1:建立 ViewModel 和 ViewModelFactory
- 在
sleepquality
套件中建立或開啟 SleepqualityViewModel.kt。 - 建立一個
SleepQualityViewModel
類別,並將sleepNightKey
和資料庫當成引數使用。與SleepTrackerViewModel
一樣,您必須從工廠中傳入database
。您也必須從導覽中傳遞sleepNightKey
。
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}
- 在
SleepQualityViewModel
類別內定義Job
和uiScope
,並覆寫onCleared()
。
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- 如要使用與上述相同的模式返回
SleepTrackerFragment
,請宣告_navigateToSleepTracker
。執行navigateToSleepTracker
和doneNavigating()
。
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
- 針對所有睡眠品質圖片,建立一個點擊處理常式
onSetSleepQuality()
,
使用與前一個程式碼研究室相同的協同模式:
- 在
uiScope
中啟動協同程式,然後切換到 I/O 調度員。 - 使用
sleepNightKey
取得tonight
。 - 設定睡眠品質。
- 更新資料庫。
- 。
請注意,下列程式碼範例會在點擊處理常式中執行所有工作,而不是在不同的情境中分離資料庫作業。
fun onSetSleepQuality(quality: Int) {
uiScope.launch {
// IO is a thread pool for running operations that access the disk, such as
// our Room database.
withContext(Dispatchers.IO) {
val tonight = database.get(sleepNightKey) ?: return@withContext
tonight.sleepQuality = quality
database.update(tonight)
}
// Setting this state variable to true will alert the observer and trigger navigation.
_navigateToSleepTracker.value = true
}
}
- 在
sleepquality
套件中建立或開啟SleepQualityViewModelFactory.kt
,並新增SleepQualityViewModelFactory
類別,如下所示。這個類別使用的是您先前看過的樣板程式碼版本。請先檢查程式碼再進行後續操作。
class SleepQualityViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
return SleepQualityViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
步驟 2:更新 SleepqualityFragment
- 開啟
SleepQualityFragment.kt
。 - 使用
onCreateView()
後,取得application
之後,你必須取得導覽隨附的arguments
。這些引數位於SleepQualityFragmentArgs
。您必須從組合中擷取這些項目。
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
- 接著,請取得
dataSource
。
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- 請建立工廠,然後傳入
dataSource
和sleepNightKey
。
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
- 取得
ViewModel
參考資料。
val sleepQualityViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
- 將
ViewModel
新增至繫結物件。(如果您發現繫結物件發生錯誤,請暫時忽略)。
binding.sleepQualityViewModel = sleepQualityViewModel
- 新增觀察器。系統提示時,請匯入
androidx.lifecycle.Observer
。
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
sleepQualityViewModel.doneNavigating()
}
})
步驟 3:更新版面配置檔案並執行應用程式
- 開啟
fragment_sleep_quality.xml
版面配置檔案。在<data>
區塊中,為SleepQualityViewModel
新增變數。
<data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>
- 為其中六張睡眠品質圖片各新增一個點擊處理常式,如下所示。與圖片的品質評分相符。
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
- 清理及重建專案。這樣做應可解決繫結物件的所有錯誤。否則請清除快取 (檔案 > 撤銷快取 / 重新啟動),然後重新建構應用程式。
恭喜!您剛剛使用協同程式建構完整的 Room
資料庫應用程式。
您的應用程式現在成效良好。使用者可以輕觸 [開始] 和 [停止],次數不限。使用者輕觸 [停止] 後,即可輸入睡眠品質。使用者輕觸 [清除] 後,系統會在背景自動清除所有資料。不過,所有按鈕一律已啟用且可點選,而不會中斷應用程式,但可讓使用者建立不完整的睡眠之夜。
在上一項工作中,您將瞭解如何使用轉換地圖來管理按鈕顯示設定,讓使用者只能做出正確的選擇。在所有的資料都清除完畢後,您可以使用類似的方法顯示友善訊息。
步驟 1:更新按鈕狀態
建議設定按鈕狀態,這樣一開始就會只啟用 [開始] 按鈕,這表示使用者點選了按鈕。
使用者輕觸 [開始] 後,[停止] 按鈕就會啟用,[開始] 按鈕則不會啟用。[資料庫] 按鈕只有在資料庫有資料時才會啟用。
- 開啟
fragment_sleep_tracker.xml
版面配置檔案。 - 在每個按鈕中加入
android:enabled
屬性。android:enabled
屬性是一個布林值,用來指出按鈕是否已啟用。(您可以輕觸 [已啟用] 按鈕;[停用] 按鈕可以繼續)。為屬性輸入一個您在稍後定義的狀態變數值。
start_button
:
android:enabled="@{sleepTrackerViewModel.startButtonVisible}"
stop_button
:
android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"
clear_button
:
android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
- 開啟「
SleepTrackerViewModel
」並建立三個對應的變數。為每個變數指派一個測試變數。
- 當
tonight
為null
時,「開始」按鈕應啟用。 - 如果
tonight
不是null
,則應啟用 [停止] 按鈕。 - [
nights
] 按鈕只有在已啟用nights
的情況下才會啟用,因此資料庫含有睡眠夜晚。
val startButtonVisible = Transformations.map(tonight) {
it == null
}
val stopButtonVisible = Transformations.map(tonight) {
it != null
}
val clearButtonVisible = Transformations.map(nights) {
it?.isNotEmpty()
}
- 執行您的應用程式,然後使用按鈕進行實驗。
步驟 2:使用 Snackbar 通知使用者
使用者清除資料庫後,使用 Snackbar
小工具向使用者顯示確認訊息。Snackbar 透過螢幕底部的訊息提供關於作業的簡短意見回饋。Snackbar 會在逾時、使用者與畫面其他畫面互動,或是使用者從 Snackbar 滑出畫面之後消失。
顯示 Snackbar 是使用者介面工作,應該會出現在片段中。決定顯示 Snackbar 的ViewModel
。如要設定和清除 Snackbar,以便在清除資料時使用相同技術,以使用觸發導覽的方法。
- 在
SleepTrackerViewModel
中,建立封裝事件。
private var _showSnackbarEvent = MutableLiveData<Boolean>()
val showSnackBarEvent: LiveData<Boolean>
get() = _showSnackbarEvent
- 然後執行
doneShowingSnackbar()
。
fun doneShowingSnackbar() {
_showSnackbarEvent.value = false
}
- 在
SleepTrackerFragment
中,在onCreateView()
中新增觀測器:
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
- 在觀察器區塊內顯示 Snackbar,並立即重設事件。
if (it == true) { // Observed state is true.
Snackbar.make(
activity!!.findViewById(android.R.id.content),
getString(R.string.cleared_message),
Snackbar.LENGTH_SHORT // How long to display the message.
).show()
sleepTrackerViewModel.doneShowingSnackbar()
}
- 在
SleepTrackerViewModel
中,使用onClear()
方法觸發事件。方法是在launch
區塊中將事件值設為true
:
_showSnackbarEvent.value = true
- 建構並執行應用程式!
Android Studio 專案:TrackMySleepqualityFinal
在這個應用程式中實作睡眠品質追蹤功能,就像使用鑰匙播放熟悉的音樂一樣。即使詳細資訊有所變更,但您在本課程中先前對程式碼研究室採取的基本動作仍維持不變。瞭解這些模式可加快程式設計速度,因為您可以重複使用現有應用程式的程式碼。以下是本課程目前使用的部分模式:
- 建立
ViewModel
和ViewModelFactory
並設定資料來源。 - 。如要區隔問題,請將點擊處理常式放在檢視模型中,然後放在片段中。
- 使用
LiveData
的封裝功能追蹤及回應狀態變更。 - 搭配
LiveData
使用轉換。 - 建立單一 ton 資料庫。
- 設定資料庫作業的協同程式。
觸發導航
您可在導覽檔中定義片段之間的可能導覽路徑。有幾種不同的方式可以觸發不同片段的瀏覽動作。包括:
- 定義
onClick
處理常式以觸發前往目的地片段的導覽作業。 - 或者,如要啟用不同片段的瀏覽功能:
- 定義
LiveData
值,以便在系統產生導航時記錄。 - 將觀察器附加至該
LiveData
值。 - 這樣一來,每當需要觸發或完成導覽時,程式碼就會變更這個值。
設定 android:enabled 屬性
android:enabled
屬性是在TextView
中定義,並由所有子類別沿用,包括Button
。android:enabled
屬性可決定是否要啟用View
。「已啟用」的含義因子類別而異。舉例來說,未啟用EditText
會禁止使用者編輯內含文字,而未啟用Button
則會禁止使用者輕觸按鈕。enabled
屬性與visibility
屬性不同。- 您可以使用轉換對應,根據其他物件或變數的狀態設定按鈕的
enabled
屬性值。
此程式碼研究室涵蓋的其他要點:
- 如要觸發通知給使用者,您可以使用與觸發導航相同的技術。
- 您可以使用
Snackbar
通知使用者。
Udacity 課程:
Android 開發人員說明文件:
這個部分會列出在代碼研究室中,受老師主導的課程作業的可能學生作業。由老師自行決定要執行下列動作:
- 視需要指派家庭作業。
- 告知學生如何提交家庭作業。
- 批改家庭作業。
老師可視需要使用這些建議,並視情況指派其他合適的家庭作業。
如果您是自行操作本程式碼研究室,歡迎透過這些家庭作業來測試自己的知識。
回答這些問題
第 1 題
如要讓應用程式觸發導覽的瀏覽方式,請使用 LiveData
值來表示是否要觸發導覽。
使用 LiveData
值 (gotoBlueFragment
) 觸發從紅色片段到藍色片段的導覽的步驟為何?(可複選):
- 在
ViewModel
中定義LiveData
值gotoBlueFragment
。 - 在
RedFragment
中觀察gotoBlueFragment
值。請實作observe{}
程式碼,以便在適當情況下前往BlueFragment
,然後重設gotoBlueFragment
的值,表示導覽已完成。 - 請確認您的程式碼會將
gotoBlueFragment
變數設為該值,在應用程式需要從RedFragment
改為BlueFragment
時觸發導航。 - 請確認您的程式碼定義了
onClick
的View
處理常式,以便使用者點擊BlueFragment
以瀏覽onClick
處理常式goToBlueFragment
的值。
問題 2
您可以透過 LiveData
來變更是否啟用 Button
(可點擊)。您會如何確保應用程式會變更 UpdateNumber
按鈕,讓:
- 如果
myNumber
的值大於 5,系統就會啟用這個按鈕。 - 如果
myNumber
等於或小於 5,系統就不會啟用這個按鈕。
假設包含 UpdateNumber
按鈕的版面配置包含 NumbersViewModel
的 <data>
變數,如下所示:
<data> <variable name="NumbersViewModel" type="com.example.android.numbersapp.NumbersViewModel" /> </data>
假設版面配置檔案中的按鈕 ID 如下:
android:id="@+id/update_number_button"
還需要採取什麼行動?請選取所有適用選項。
- 在
NumbersViewModel
類別中,定義代表數字的LiveData
變數myNumber
。您也可以在myNumber
變數上呼叫Transform.map()
來定義其設定的變數。這個變數會傳回一個布林值,指出數字是否大於 5。
具體來說,在「ViewModel
」中加入下列程式碼:
val myNumber: LiveData<Int>
val enableUpdateNumberButton = Transformations.map(myNumber) {
myNumber > 5
}
- 在 XML 版面配置中,將
update_number_button button
的android:enabled
屬性設為NumberViewModel.enableUpdateNumbersButton
。
android:enabled="@{NumbersViewModel.enableUpdateNumberButton}"
- 在使用
NumbersViewModel
類別的Fragment
中,在按鈕的enabled
屬性中加入觀察器。
具體來說,在「Fragment
」中加入下列程式碼:
// Observer for the enabled attribute
viewModel.enabled.observe(this, Observer<Boolean> { isEnabled ->
myNumber > 5
})
- 在版面配置檔案中,將
update_number_button button
的android:enabled
屬性設為"Observable"
。
開始下一門課程:
如要瞭解本課程中其他程式碼研究室的連結,請參閱 Android Kotlin 基礎程式碼程式碼到達網頁。