這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁。
簡介
在先前的程式碼研究室中,您已在 GuessTheWord 應用程式中使用 ViewModel
,確保應用程式資料在裝置設定變更後仍然有效。在本程式碼研究室中,您將瞭解如何整合 LiveData
與 ViewModel
類別中的資料。LiveData
是 Android 架構元件之一,可讓您建構資料物件,在基礎資料庫變更時通知檢視區塊。
如要使用 LiveData
類別,請設定「觀察程式」(例如活動或片段),觀察應用程式資料的變更。LiveData
具備生命週期感知功能,因此只會更新處於有效生命週期狀態的應用程式元件觀察器。
必備知識
- 如何使用 Kotlin 建構基本 Android 應用程式。
- 如何瀏覽應用程式的目的地。
- 活動和片段生命週期。
- 如何在應用程式中使用
ViewModel
物件。 - 如何使用
ViewModelProvider.Factory
介面建立ViewModel
物件。
課程內容
LiveData
物件的實用之處。- 如何將
LiveData
新增至儲存在ViewModel
中的資料。 - 使用
MutableLiveData
的時機和方式。 - 如何新增觀察器方法以觀察
LiveData.
中的變化。 - 如何使用幕後屬性封裝
LiveData
。 - 如何讓 UI 控制器與對應的
ViewModel
進行通訊。
學習內容
- 在 GuessTheWord 應用程式中,針對字詞和分數使用
LiveData
。 - 新增觀察器,在字詞或分數變更時接收通知。
- 更新顯示變更值的文字檢視區塊。
- 使用
LiveData
觀察器模式新增遊戲完成事件。 - 實作「Play Again」按鈕。
在第 5 課的程式碼研究室中,您將從範例程式碼開始開發 GuessTheWord 應用程式。GuessTheWord 是雙人比手畫腳遊戲,玩家要彼此合作,盡可能獲得最高分。
第一位玩家看著應用程式中的字詞,輪流表演每個字詞,但要確保第二位玩家看不到字詞。第二位玩家嘗試猜測字詞。
如要開始遊戲,第一位玩家要在裝置上開啟應用程式,並看到一個字詞,例如「吉他」,如下方螢幕截圖所示。
第一位玩家要表演這個字詞,但不能說出該字詞。
- 第二位玩家猜對字詞後,第一位玩家會按下「Got It」按鈕,計數會增加一,並顯示下一個字詞。
- 如果第二位玩家猜不出單字,第一位玩家可以按下「略過」按鈕,這樣一來,計數就會減少 1,並跳到下一個單字。
- 如要結束遊戲,請按下「結束遊戲」按鈕。(這項功能不包含在系列第一個程式碼研究室的範例程式碼中)。
在本程式碼研究室中,您要改善 GuessTheWord 應用程式,加入一個事件,在使用者瀏覽應用程式中的所有字詞後結束遊戲。您也會在分數片段中加入「Play Again」 按鈕,讓使用者可以再次玩遊戲。
標題畫面 | 遊戲畫面 | 分數畫面 |
在這項工作中,您將找出並執行本程式碼研究室的範例程式碼。您可以使用先前程式碼研究室中建構的 GuessTheWord 應用程式做為範例程式碼,也可以下載範例應用程式。
- (選用) 如果您未使用上一個程式碼研究室的程式碼,請下載本程式碼研究室的範例程式碼。解壓縮程式碼,並在 Android Studio 中開啟專案。
- 執行應用程式並玩遊戲。
- 請注意,「略過」按鈕會顯示下一個字詞,並將分數減少 1 分;「答對了」按鈕會顯示下一個字詞,並將分數增加 1 分。結束遊戲按鈕會結束遊戲。
LiveData
是可觀察的資料容器類別,可感知生命週期。舉例來說,您可以在 GuessTheWord 應用程式中,將 LiveData
包裝在目前的分數周圍。在本程式碼研究室中,您將瞭解 LiveData
的幾項特徵:
LiveData
可觀察,這表示當LiveData
物件保存的資料變更時,觀察器會接收通知。LiveData
可保存資料;LiveData
包裝函式可與任何資料搭配使用LiveData
具備生命週期感知功能,代表其只會更新處於有效生命週期狀態 (例如STARTED
或RESUMED
) 的觀察器。
在這項工作中,您會學習如何將 GameViewModel
中的目前分數和目前字詞資料轉換為 LiveData
,以將任何資料型別包裝成 LiveData
物件。在後續工作中,您會將觀察器新增至此類 LiveData
物件,並瞭解如何觀察 LiveData
。
步驟 1:變更分數和字詞,改用 LiveData
- 在
screens/game
套件下,開啟GameViewModel
檔案。 - 將變數
score
和word
的類型變更為MutableLiveData
。MutableLiveData
是LiveData
,其值可以變更。MutableLiveData
屬於一般類別,因此您需要指定其保存的資料類型。
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
- 在
GameViewModel
中,於init
區塊內初始化score
和word
。如要變更LiveData
變數的值,請對該變數使用setValue()
方法。在 Kotlin 中,您可以使用value
屬性呼叫setValue()
。
init {
word.value = ""
score.value = 0
...
}
步驟 2:更新 LiveData 物件參照
score
和 word
變數現在屬於 LiveData
類型。在這個步驟中,您會使用 value
屬性,變更對這些變數的參照。
- 在
GameViewModel
的onSkip()
方法中,將score
變更為score.value
。請注意,錯誤訊息指出score
可能為null
。您將在下一個步驟修正此錯誤。 - 如要解決這項錯誤,請在
onSkip()
的score.value
中新增null
檢查。然後在score
上呼叫minus()
函式,以執行null
安全減法。
fun onSkip() {
if (!wordList.isEmpty()) {
score.value = (score.value)?.minus(1)
}
nextWord()
}
- 以相同方式更新
onCorrect()
方法:在score
變數中新增null
檢查,並使用plus()
函式。
fun onCorrect() {
if (!wordList.isEmpty()) {
score.value = (score.value)?.plus(1)
}
nextWord()
}
- 在
GameViewModel
的nextWord()
方法中,將word
參照變更為word
.
value
。
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word.value = wordList.removeAt(0)
}
}
- 在
GameFragment
的updateWordText()
方法中,將viewModel
.word
的參照變更為viewModel
.
word
.
value.
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = viewModel.word.value
}
- 在
GameFragment
的updateScoreText()
方法中,將viewModel
.score
的參照變更為viewModel
.
score
.
value.
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.value.toString()
}
- 在
GameFragment
的gameFinished()
方法中,將viewModel
.score
的參照變更為viewModel
.
score
.
value
。新增必要的null
安全檢查。
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score.value?:0
NavHostFragment.findNavController(this).navigate(action)
}
- 請確認程式碼中沒有錯誤。編譯並執行應用程式。應用程式的功能應與先前相同。
這項工作與前一個工作密切相關,您在前一個工作中將分數和字詞資料轉換為 LiveData
物件。在這項工作中,您要將 Observer
物件附加至這些 LiveData
物件。
- 在
GameFragment,
的onCreateView()
方法中,將Observer
物件附加至目前分數的LiveData
物件viewModel.score
。使用observe()
方法,並將程式碼放在viewModel
初始化之後。使用 lambda 運算式簡化程式碼。(Lambda 運算式是未宣告的匿名函式,但會立即以運算式的形式傳遞。)
viewModel.score.observe(this, Observer { newScore ->
})
解決對 Observer
的參照。如要匯入 androidx.lifecycle.Observer
,請點選 Observer
,按下 Alt+Enter
鍵 (Mac 上為 Option+Enter
鍵)。
- 當觀察的
LiveData
物件保存的資料變更時,您剛建立的觀察器會收到事件。在觀察器內,使用新分數更新分數TextView
。
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- 將
Observer
物件附加至目前的字詞LiveData
物件。方法與將Observer
物件附加至目前分數相同。
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})
當 score
或 word
的值變更時,畫面上顯示的 score
或 word
現在會自動更新。
- 在
GameFragment
中,刪除updateWordText()
和updateScoreText()
方法,以及所有對這些方法的參照。您已無需使用這些觀察器,因為文字檢視畫面會由LiveData
觀察器方法更新。 - 執行應用程式。您的遊戲應用程式應可照常運作,但現在會使用
LiveData
和LiveData
觀察器。
封裝是限制直接存取部分物件欄位的方式。封裝物件時,您將利用一組公開方法修改私人內部欄位。運用封裝,您可以控管其他類別操控這些內部欄位的方式。
在目前的程式碼中,任何外部類別都可以使用 value
屬性修改 score
和 word
變數,例如使用 viewModel.score.value
。在本程式碼研究室中開發的應用程式可能不重要,但在正式版應用程式中,您會想控管 ViewModel
物件中的資料。
只有 ViewModel
才能編輯應用程式中的資料。不過,UI 控制器需要讀取資料,因此資料欄位無法完全私密。如要封裝應用程式的資料,請同時使用 MutableLiveData
和 LiveData
物件。
MutableLiveData
與LiveData
的比較:
- 顧名思義,
MutableLiveData
物件中的資料可以變更。ViewModel
內的資料應可編輯,因此會使用MutableLiveData
。 - 您可以讀取
LiveData
物件中的資料,但無法變更。在ViewModel
外部,資料應可供讀取,但無法編輯,因此資料應以LiveData
的形式呈現。
如要執行這項策略,請使用 Kotlin 幕後屬性。幕後屬性可讓您從 getter 傳回確切物件以外的項目。在這項工作中,您要在「猜單字」應用程式中,為 score
和 word
物件實作支援屬性。
為分數和字詞新增幕後屬性
- 在
GameViewModel
中,將目前的score
物件設為private
。 - 如要遵循幕後屬性使用的命名慣例,請將
score
變更為_score
。_score
屬性現在是遊戲分數的可變動版本,供內部使用。 - 建立
LiveData
類型的公開版本,並命名為score
。
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
- 您看到初始化錯誤。發生這項錯誤的原因是
GameFragment
內部的score
是LiveData
參照,而score
無法再存取其設定器。如要進一步瞭解 Kotlin 中的 getter 和 setter,請參閱「Getter 和 Setter」。
如要解決錯誤,請在GameViewModel
中覆寫score
物件的get()
方法,並傳回支援屬性_score
。
val score: LiveData<Int>
get() = _score
- 在
GameViewModel
中,將score
的參照變更為內部可變動版本_score
。
init {
...
_score.value = 0
...
}
...
fun onSkip() {
if (!wordList.isEmpty()) {
_score.value = (score.value)?.minus(1)
}
...
}
fun onCorrect() {
if (!wordList.isEmpty()) {
_score.value = (score.value)?.plus(1)
}
...
}
- 將
word
物件重新命名為_word
,並為其新增幕後屬性,就像您對score
物件所做的一樣。
// The current word
private val _word = MutableLiveData<String>()
val word: LiveData<String>
get() = _word
...
init {
_word.value = ""
...
}
...
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
_word.value = wordList.removeAt(0)
}
}
做得好,您已封裝 LiveData
物件 word
和 score
。
目前,使用者輕觸「End Game」按鈕時,應用程式會導覽至分數畫面。您也希望應用程式在玩家輪流說完所有字詞後,導覽至分數畫面。玩家完成最後一個字後,您希望遊戲自動結束,使用者不必輕觸按鈕。
如要實作這項功能,您需要觸發事件,並在顯示所有字詞時,從 ViewModel
將事件傳達至片段。為此,您可以使用 LiveData
觀察器模式,模擬遊戲結束事件。
觀察者模式
觀察器模式是一種軟體設計模式,它會指定物件之間的通訊:可觀察項 (觀察的「主體」) 和觀察器。可觀察物件會通知觀察器狀態變更。
以這個應用程式中的 LiveData
為例,可觀測的項目 (主體) 是 LiveData
物件,而觀測器是 UI 控制器 (例如片段) 中的方法。每當 LiveData
內包裝的資料變更時,就會發生狀態變更。LiveData
類別對於從 ViewModel
傳輸資料至片段至關重要。
步驟 1:使用 LiveData 偵測遊戲結束事件
在這項工作中,您要使用 LiveData
觀察器模式,為遊戲結束事件建立模型。
- 在
GameViewModel
中,建立名為_eventGameFinish
的Boolean
MutableLiveData
物件。這個物件會保存遊戲結束事件。 - 初始化
_eventGameFinish
物件後,請建立並初始化名為eventGameFinish
的支援屬性。
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
get() = _eventGameFinish
- 在
GameViewModel
中新增onGameFinish()
方法。在方法中,將遊戲完成事件eventGameFinish
設為true
。
/** Method for the game completed event **/
fun onGameFinish() {
_eventGameFinish.value = true
}
- 在
GameViewModel
的nextWord()
方法中,如果字詞清單為空白,請結束遊戲。
private fun nextWord() {
if (wordList.isEmpty()) {
onGameFinish()
} else {
//Select and remove a _word from the list
_word.value = wordList.removeAt(0)
}
}
- 在
GameFragment
的onCreateView()
中,初始化viewModel
後,將觀察器附加至eventGameFinish
。請使用observe()
方法。在 lambda 函式中呼叫gameFinished()
方法。
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
if (hasFinished) gameFinished()
})
- 執行應用程式,使用所有字詞進行遊戲。應用程式會自動前往分數畫面,而不是停留在遊戲片段,直到你輕觸「結束遊戲」為止。
字詞清單清空後,系統會設定eventGameFinish
,並呼叫遊戲片段中相關聯的觀察器方法,然後應用程式會導覽至畫面片段。 - 您新增的程式碼導致生命週期問題。如要瞭解問題,請在
GameFragment
類別中,註解gameFinished()
方法中的導覽程式碼。請務必在方法中保留Toast
訊息。
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
// val action = GameFragmentDirections.actionGameToScore()
// action.score = viewModel.score.value?:0
// NavHostFragment.findNavController(this).navigate(action)
}
- 執行應用程式,使用所有字詞進行遊戲。遊戲畫面底部會短暫顯示「遊戲剛結束」的浮動式訊息,這是預期行為。
現在請旋轉裝置或模擬器。浮動式訊息會再次顯示!再旋轉裝置幾次,每次應該都會看到訊息。這是錯誤,因為遊戲結束時,祝賀訊息只應顯示一次。每次重新建立片段時,都不應顯示訊息方塊。您將在下一個工作中解決這個問題。
步驟 2:重設遊戲完成事件
通常 LiveData
只會在資料變更時,向觀察器提供更新。但若觀測器從閒置狀態變更為活動狀態,也會收到更新。
這就是應用程式中遊戲結束訊息會重複觸發的原因。螢幕旋轉後,遊戲片段會重新建立,並從非使用中狀態轉為使用中狀態。片段中的觀察器會重新連線至現有的 ViewModel
,並接收目前的資料。系統會重新觸發 gameFinished()
方法,並顯示訊息方塊。
在這項工作中,您會在 GameViewModel
中重設 eventGameFinish
旗標,修正這個問題並只顯示一次訊息。
- 在
GameViewModel
中,新增onGameFinishComplete()
方法來重設遊戲完成事件_eventGameFinish
。
/** Method for the game completed event **/
fun onGameFinishComplete() {
_eventGameFinish.value = false
}
- 在
GameFragment
中,於gameFinished()
結尾,對viewModel
物件呼叫onGameFinishComplete()
。(暫時將gameFinished()
中的導覽程式碼加上註解)。
private fun gameFinished() {
...
viewModel.onGameFinishComplete()
}
- 執行應用程式,然後進行遊戲。瀏覽所有單字,然後變更裝置的螢幕方向。系統只會顯示一次訊息。
- 在
GameFragment
的gameFinished()
方法內,取消註解導覽程式碼。
如要在 Android Studio 中取消註解,請選取已註解的行,然後按下Control+/
鍵 (Mac 上的Command+/
鍵)。
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score.value?:0
findNavController(this).navigate(action)
viewModel.onGameFinishComplete()
}
如果 Android Studio 顯示提示,請匯入 androidx.navigation.fragment.NavHostFragment.findNavController
。
- 執行應用程式,然後進行遊戲。請確認應用程式會在您完成所有單字後,自動前往最終分數畫面。
太棒了!應用程式會使用 LiveData
觸發遊戲結束事件,從 GameViewModel
傳送訊息至遊戲片段,指出字詞清單為空白。遊戲片段接著會前往分數片段。
在這項工作中,您要將 ScoreViewModel
中的分數變更為 LiveData
物件,並附加觀察器。這項工作與您將 LiveData
新增至 GameViewModel
時的操作類似。
您會為了完整性而進行這些變更,確保應用程式中的所有資料都使用 LiveData
。ScoreViewModel
- 在
ScoreViewModel
中,將score
變數類型變更為MutableLiveData
。按照慣例將其重新命名為_score
,並新增幕後屬性。
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
get() = _score
- 在
ScoreViewModel
的init
區塊內,初始化_score
。您可以視需要移除或保留init
區塊中的記錄。
init {
_score.value = finalScore
}
- 在
ScoreFragment
的onCreateView()
中,初始化viewModel
後,附加分數LiveData
物件的觀察器。在 lambda 運算式中,將分數值設為分數文字檢視區塊。從ViewModel
中移除直接將分數值指派給文字檢視區塊的程式碼。
要新增的程式碼:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
要移除的程式碼:
binding.scoreText.text = viewModel.score.toString()
在 Android Studio 顯示提示時,匯入 androidx.lifecycle.Observer
。
- 執行應用程式,然後進行遊戲。應用程式應可照常運作,但現在會使用
LiveData
和觀察器更新分數。
在這項工作中,您要在分數畫面上新增「Play Again」按鈕,並使用 LiveData
事件實作其點擊事件監聽器。這個按鈕會觸發事件,從分數畫面導覽至遊戲畫面。
應用程式的範例程式碼包含「Play Again」按鈕,但該按鈕已隱藏。
- 在
res/layout/score_fragment.xml
中,針對play_again_button
按鈕,將visibility
屬性的值變更為visible
。
<Button
android:id="@+id/play_again_button"
...
android:visibility="visible"
/>
- 在
ScoreViewModel
中,新增LiveData
物件來保留名為_eventPlayAgain
的Boolean
。這個物件用於儲存LiveData
事件,以便從分數畫面導覽至遊戲畫面。
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
get() = _eventPlayAgain
- 在
ScoreViewModel
中,定義設定及重設事件的方法_eventPlayAgain
。
fun onPlayAgain() {
_eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
_eventPlayAgain.value = false
}
- 在
ScoreFragment
中,為eventPlayAgain
新增觀察器。將程式碼放在onCreateView()
的結尾,也就是return
陳述式之前。在 lambda 運算式中,返回遊戲畫面並重設eventPlayAgain
。
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
if (playAgain) {
findNavController().navigate(ScoreFragmentDirections.actionRestart())
viewModel.onPlayAgainComplete()
}
})
請在 Android Studio 出現提示時匯入 androidx.navigation.fragment.findNavController
。
- 在
ScoreFragment
的onCreateView()
內,將點按事件監聽器新增至「PlayAgain」按鈕,並呼叫viewModel
.onPlayAgain()
。
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- 執行應用程式,然後進行遊戲。遊戲結束時,分數畫面會顯示最終分數和「Play Again」按鈕。輕觸「PlayAgain」PlayAgain按鈕,應用程式就會切換至遊戲畫面,讓您再次玩遊戲。
好極了!您已變更應用程式的架構,在 ViewModel
中使用 LiveData
物件,並將觀察器附加至 LiveData
物件。當 LiveData
保存的值變更時,LiveData
會通知觀察器物件。
Android Studio 專案:GuessTheWord
LiveData
LiveData
是可觀察的資料容器類別,可感知生命週期,屬於 Android 架構元件之一。- 您可以使用
LiveData
,在資料更新時自動更新 UI。 LiveData
可觀察,這表示當LiveData
物件保存的資料變更時,活動或片段等觀察器會接收通知。LiveData
可保存資料;包裝函式可與任何資料搭配使用。LiveData
具備生命週期感知功能,代表其只會更新處於有效生命週期狀態 (例如STARTED
或RESUMED
) 的觀察器。
新增 LiveData
- 將
ViewModel
中的資料變數類型變更為LiveData
或MutableLiveData
。
MutableLiveData
是值可變更的 LiveData
物件。MutableLiveData
屬於一般類別,因此您需要指定其保存的資料類型。
- 如要變更
LiveData
所保存的資料值,請對LiveData
變數使用setValue()
方法。
封裝 LiveData
ViewModel
內的LiveData
應可編輯。在ViewModel
外部,LiveData
應可供讀取。這可以使用 Kotlin 幕後屬性實作。- Kotlin 幕後屬性可讓您從 getter 傳回確切物件以外的項目。
- 如要封裝
LiveData
,請在ViewModel
中使用private
MutableLiveData
,並在ViewModel
外部傳回LiveData
支援屬性。
可觀測的 LiveData
LiveData
遵循觀測器模式。「可觀察」的對象是LiveData
物件,而觀察者是 UI 控制器 (例如片段) 中的方法。每當LiveData
內包裝的資料變更時,UI 控制器中的觀察器方法就會收到通知。- 如要讓
LiveData
可供觀察,請使用observe()
方法,將觀察器物件附加至觀察器 (例如活動和片段) 中的LiveData
參照。 - 這個
LiveData
觀察器模式可用於從ViewModel
與 UI 控制器通訊。
Udacity 課程:
Android 開發人員說明文件:
其他:
- Kotlin 中的幕後屬性
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
回答問題
第 1 題
你會以何種方式封裝儲存於 ViewModel
中的 LiveData
,以便外部物件不需加以更新即可讀取資料?
- 在
ViewModel
物件中,將資料的資料類型變更為private
LiveData
。使用幕後屬性來公開型別MutableLiveData
的唯讀資料。 - 在
ViewModel
物件中,將資料的資料類型變更為private
MutableLiveData
。使用幕後屬性來公開型別LiveData
的唯讀資料。 - 在 UI 控制器中,將資料的資料類型變更為
private
MutableLiveData
。使用幕後屬性來公開型別LiveData
的唯讀資料。 - 在
ViewModel
物件內部,將資料的資料型別變更為LiveData
。使用幕後屬性來公開型別LiveData
的唯讀資料。
第 2 題
當 UI 控制器處於下列何種狀態時,LiveData
會更新 UI 控制器 (例如片段)?
- 已重新啟用
- 在背景中
- 已暫停
- 已停止
第 3 題
在 LiveData
觀察器模式中,可觀察的項目 (觀察對象) 為何?
- Observer 方法
LiveData
物件中的資料- UI 控制器
ViewModel
物件
開始下一個課程:
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面。