Codelab นี้เป็นส่วนหนึ่งของหลักสูตรหลักพื้นฐานของ Android Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin
บทนำ
ในโค้ดแล็บก่อนหน้านี้ คุณใช้ ViewModel ในแอป GuessTheWord เพื่ออนุญาตให้ข้อมูลของแอปยังคงอยู่เมื่อมีการเปลี่ยนแปลงการกำหนดค่าอุปกรณ์ ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีผสานรวม LiveData กับข้อมูลในคลาส ViewModel LiveData ซึ่งเป็นหนึ่งในคอมโพเนนต์สถาปัตยกรรมของ Android ช่วยให้คุณสร้างออบเจ็กต์ข้อมูลที่จะแจ้งให้มุมมองทราบเมื่อฐานข้อมูลพื้นฐานมีการเปลี่ยนแปลง
หากต้องการใช้คลาส LiveData คุณจะต้องตั้งค่า "ผู้สังเกตการณ์" (เช่น กิจกรรมหรือ Fragment) ที่สังเกตการเปลี่ยนแปลงในข้อมูลของแอป LiveData ตระหนักถึงวงจร ดังนั้นจึงอัปเดตเฉพาะ Observer ของคอมโพเนนต์แอปที่อยู่ในสถานะวงจรที่ใช้งานอยู่
สิ่งที่คุณควรทราบอยู่แล้ว
- วิธีสร้างแอป Android พื้นฐานใน Kotlin
- วิธีไปยังส่วนต่างๆ ของแอป
- วงจรของกิจกรรมและ Fragment
- วิธีใช้
ViewModelออบเจ็กต์ในแอป - วิธีสร้าง
ViewModelออบเจ็กต์โดยใช้อินเทอร์เฟซของViewModelProvider.Factory
สิ่งที่คุณจะได้เรียนรู้
- อะไรที่ทำให้ออบเจ็กต์
LiveDataมีประโยชน์ - วิธีเพิ่ม
LiveDataลงในข้อมูลที่จัดเก็บไว้ในViewModel - กรณีที่ควรใช้และวิธีใช้
MutableLiveData - วิธีเพิ่มเมธอด Observer เพื่อสังเกตการเปลี่ยนแปลงใน
LiveData. - วิธีแคปซูล
LiveDataโดยใช้พร็อพเพอร์ตี้สำรอง - วิธีสื่อสารระหว่างตัวควบคุม UI กับ
ViewModelที่เกี่ยวข้อง
สิ่งที่คุณต้องดำเนินการ
- ใช้
LiveDataสำหรับคำและคะแนนในแอป GuessTheWord - เพิ่มผู้สังเกตการณ์ที่สังเกตได้เมื่อมีการเปลี่ยนแปลงคำหรือคะแนน
- อัปเดตมุมมองข้อความที่แสดงค่าที่เปลี่ยนแปลง
- ใช้
LiveDataรูปแบบ Observer เพื่อเพิ่มเหตุการณ์เกมจบ - ใช้ปุ่มเล่นอีกครั้ง
ใน Codelab บทที่ 5 คุณจะได้พัฒนาแอป GuessTheWord โดยเริ่มจากโค้ดเริ่มต้น GuessTheWord เป็นเกมสไตล์ใบ้คำสำหรับผู้เล่น 2 คน โดยผู้เล่นจะต้องร่วมมือกันเพื่อให้ได้คะแนนสูงสุดเท่าที่จะเป็นไปได้
ผู้เล่นคนแรกดูคำในแอปและแสดงท่าทางของคำนั้นทีละคำ โดยต้องไม่ให้ผู้เล่นคนที่ 2 เห็นคำ ผู้เล่นคนที่ 2 พยายามทายคำ
หากต้องการเล่นเกม ผู้เล่นคนแรกจะเปิดแอปในอุปกรณ์และเห็นคำ เช่น "กีตาร์" ดังที่แสดงในภาพหน้าจอด้านล่าง
ผู้เล่นคนแรกจะแสดงท่าทางตามคำนั้น โดยระมัดระวังไม่ให้พูดคำนั้นออกมาจริงๆ
- เมื่อผู้เล่นคนที่ 2 ทายคำถูกต้อง ผู้เล่นคนแรกจะกดปุ่มเข้าใจแล้ว ซึ่งจะเพิ่มจำนวนขึ้น 1 และแสดงคำถัดไป
- หากผู้เล่นคนที่ 2 เดาคำไม่ได้ ผู้เล่นคนแรกจะกดปุ่มข้าม ซึ่งจะลดจำนวนลง 1 และข้ามไปยังคำถัดไป
- หากต้องการจบเกม ให้กดปุ่มจบเกม (ฟังก์ชันนี้ไม่ได้อยู่ในโค้ดเริ่มต้นสำหรับโค้ดแล็บแรกในชุด)
ในโค้ดแล็บนี้ คุณจะปรับปรุงแอป GuessTheWord โดยการเพิ่มเหตุการณ์เพื่อจบเกมเมื่อผู้ใช้ดูคำทั้งหมดในแอป นอกจากนี้ คุณยังเพิ่มปุ่มเล่นอีกครั้ง ใน Fragment คะแนนเพื่อให้ผู้ใช้เล่นเกมได้อีกครั้ง
หน้าจอชื่อ |
หน้าจอเกม |
หน้าจอคะแนน |
ในงานนี้ คุณจะได้ค้นหาและเรียกใช้โค้ดเริ่มต้นสำหรับ Codelab นี้ คุณสามารถใช้แอป GuessTheWord ที่สร้างใน Codelab ก่อนหน้าเป็นโค้ดเริ่มต้น หรือจะดาวน์โหลดแอปเริ่มต้นก็ได้
- (ไม่บังคับ) หากไม่ได้ใช้โค้ดจาก Codelab ก่อนหน้า ให้ดาวน์โหลดโค้ดเริ่มต้นสำหรับ Codelab นี้ คลายซิปรหัสและเปิดโปรเจ็กต์ใน Android Studio
- เรียกใช้แอปและเล่นเกม
- โปรดทราบว่าปุ่มข้ามจะแสดงคำถัดไปและลดคะแนนลง 1 คะแนน ส่วนปุ่มเข้าใจแล้วจะแสดงคำถัดไปและเพิ่มคะแนนขึ้น 1 คะแนน ปุ่มจบเกมจะสิ้นสุดเกม
LiveData คือคลาสที่เก็บข้อมูลที่สังเกตได้ซึ่งรับรู้ถึงวงจร เช่น คุณสามารถใส่ LiveData รอบคะแนนปัจจุบันในแอป GuessTheWord ได้ ใน Codelab นี้ คุณจะได้เรียนรู้ลักษณะหลายอย่างของ LiveData ดังนี้
LiveDataสามารถสังเกตได้ ซึ่งหมายความว่าผู้สังเกตการณ์จะได้รับการแจ้งเตือนเมื่อข้อมูลที่ออบเจ็กต์LiveDataเปลี่ยนแปลงLiveDataจัดเก็บข้อมูลLiveDataเป็น Wrapper ที่ใช้กับข้อมูลใดก็ได้LiveDataตระหนักถึงวงจรของกิจกรรม ซึ่งหมายความว่าจะอัปเดตเฉพาะผู้สังเกตการณ์ที่อยู่ในสถานะวงจรของกิจกรรมที่ใช้งานอยู่ เช่นSTARTEDหรือRESUMED
ในงานนี้ คุณจะได้เรียนรู้วิธีรวมข้อมูลประเภทใดก็ได้ไว้ในออบเจ็กต์ LiveData โดยการแปลงคะแนนปัจจุบันและข้อมูลคำปัจจุบันใน GameViewModel เป็น LiveData ในงานที่ทำในภายหลัง คุณจะเพิ่ม Observer ลงในออบเจ็กต์ LiveData เหล่านี้ และเรียนรู้วิธีสังเกต LiveData
ขั้นตอนที่ 1: เปลี่ยนคะแนนและคำเพื่อใช้ LiveData
- เปิดไฟล์
GameViewModelในแพ็กเกจscreens/game - เปลี่ยนประเภทของตัวแปร
scoreและwordเป็นMutableLiveDataMutableLiveDataคือLiveDataที่เปลี่ยนค่าได้MutableLiveDataเป็นคลาสทั่วไป ดังนั้นคุณจึงต้องระบุประเภทข้อมูลที่คลาสนี้จัดเก็บ
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()- ใน
GameViewModelภายในบล็อกinitให้เริ่มต้นscoreและwordหากต้องการเปลี่ยนค่าของตัวแปรLiveDataให้ใช้วิธีsetValue()กับตัวแปร ใน Kotlin คุณเรียกใช้setValue()ได้โดยใช้พร็อพเพอร์ตี้value
init {
word.value = ""
score.value = 0
...
}ขั้นตอนที่ 2: อัปเดตการอ้างอิงออบเจ็กต์ LiveData
ตอนนี้ตัวแปร score และ word มีประเภทเป็น LiveData แล้ว ในขั้นตอนนี้ คุณจะเปลี่ยนการอ้างอิงตัวแปรเหล่านี้โดยใช้พร็อพเพอร์ตี้ value
- ใน
GameViewModelในวิธีการonSkip()ให้เปลี่ยนscoreเป็นscore.valueสังเกตข้อผิดพลาดเกี่ยวกับscoreที่อาจเป็นnullคุณจะแก้ไขข้อผิดพลาดนี้ในขั้นตอนถัดไป - หากต้องการแก้ไขข้อผิดพลาด ให้เพิ่ม
nullเช็คscore.valueในonSkip()จากนั้นเรียกใช้ฟังก์ชันminus()ในscoreซึ่งจะทำการลบด้วยnull-safety
fun onSkip() {
if (!wordList.isEmpty()) {
score.value = (score.value)?.minus(1)
}
nextWord()
}- อัปเดตเมธอด
onCorrect()ในลักษณะเดียวกัน โดยเพิ่มการตรวจสอบnullลงในตัวแปรscoreและใช้ฟังก์ชัน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ใช้นิพจน์แลมบ์ดาเพื่อลดความซับซ้อนของโค้ด (นิพจน์แลมบ์ดาคือฟังก์ชันที่ไม่ระบุชื่อซึ่งไม่ได้ประกาศ แต่จะส่งเป็นนิพจน์ทันที)
viewModel.score.observe(this, Observer { newScore ->
})แก้ไขการอ้างอิงถึง Observer โดยคลิก Observer กด Alt+Enter (Option+Enter ใน Mac) แล้วนำเข้า androidx.lifecycle.Observer
- Observer ที่คุณเพิ่งสร้างจะได้รับเหตุการณ์เมื่อข้อมูลที่ออบเจ็กต์
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และLiveDataObserver
การห่อหุ้มเป็นวิธีจำกัดการเข้าถึงโดยตรงไปยังฟิลด์บางรายการของออบเจ็กต์ เมื่อห่อหุ้มออบเจ็กต์ คุณจะแสดงชุดเมธอดสาธารณะที่แก้ไขฟิลด์ภายในแบบส่วนตัว การใช้การห่อหุ้มช่วยให้คุณควบคุมวิธีที่คลาสอื่นๆ จัดการฟิลด์ภายในเหล่านี้ได้
ในโค้ดปัจจุบัน คลาสภายนอกใดๆ ก็สามารถแก้ไขตัวแปร score และ word ได้โดยใช้พร็อพเพอร์ตี้ value เช่น ใช้ viewModel.score.value ซึ่งอาจไม่สำคัญในแอปที่คุณกำลังพัฒนาในโค้ดแล็บนี้ แต่ในแอปเวอร์ชันที่ใช้งานจริง คุณจะต้องควบคุมข้อมูลในออบเจ็กต์ ViewModel
มีเพียง ViewModel เท่านั้นที่ควรแก้ไขข้อมูลในแอป แต่ตัวควบคุม UI ต้องอ่านข้อมูล ดังนั้นฟิลด์ข้อมูลจึงไม่สามารถเป็นแบบส่วนตัวได้อย่างสมบูรณ์ หากต้องการแคปซูลข้อมูลของแอป คุณต้องใช้ออบเจ็กต์ MutableLiveData และ LiveData
MutableLiveData เทียบกับ LiveData:
- คุณเปลี่ยนข้อมูลในออบเจ็กต์
MutableLiveDataได้ตามชื่อ ภายในViewModelควรแก้ไขข้อมูลได้ จึงใช้MutableLiveData - อ่านข้อมูลในออบเจ็กต์
LiveDataได้ แต่เปลี่ยนแปลงไม่ได้ จากภายนอกViewModelข้อมูลควรอ่านได้ แต่แก้ไขไม่ได้ ดังนั้นจึงควรแสดงข้อมูลเป็นLiveData
หากต้องการใช้กลยุทธ์นี้ คุณต้องใช้พร็อพเพอร์ตี้สำรองของ Kotlin พร็อพเพอร์ตี้สำรองช่วยให้คุณส่งคืนค่าจากตัวดึงข้อมูลอื่นที่ไม่ใช่ออบเจ็กต์ที่แน่นอนได้ ในงานนี้ คุณจะได้ใช้พร็อพเพอร์ตี้สำรองสำหรับออบเจ็กต์ score และ word ในแอป GuessTheWord
เพิ่มพร็อพเพอร์ตี้สำรองให้กับคะแนนและคำ
- ใน
GameViewModelให้ทำให้ออบเจ็กต์scoreปัจจุบันเป็นprivate - หากต้องการทำตามรูปแบบการตั้งชื่อที่ใช้ในพร็อพเพอร์ตี้สำรอง ให้เปลี่ยน
scoreเป็น_scoreตอนนี้พร็อพเพอร์ตี้_scoreเป็นเวอร์ชันที่แก้ไขได้ของคะแนนเกม ซึ่งใช้ภายใน - สร้างเวอร์ชันสาธารณะของ
LiveDataประเภทที่ชื่อscore
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>- คุณเห็นข้อผิดพลาดในการเริ่มต้น ข้อผิดพลาดนี้เกิดขึ้นเนื่องจากภายใน
GameFragmentscoreเป็นการอ้างอิงLiveDataและscoreไม่สามารถเข้าถึง Setter ได้อีกต่อไป ดูข้อมูลเพิ่มเติมเกี่ยวกับ Getter และ Setter ใน Kotlin ได้ที่Getter และ Setter
หากต้องการแก้ไขข้อผิดพลาด ให้แทนที่เมธอดget()สำหรับออบเจ็กต์scoreในGameViewModelและส่งคืนพร็อพเพอร์ตี้สำรอง_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แล้ว
แอปปัจจุบันจะนำผู้ใช้ไปยังหน้าจอคะแนนเมื่อแตะปุ่มจบเกม นอกจากนี้ คุณยังต้องการให้แอปไปยังหน้าจอคะแนนเมื่อผู้เล่นหมุนเวียนคำทั้งหมดแล้ว หลังจากผู้เล่นตอบคำสุดท้ายแล้ว คุณต้องการให้เกมจบโดยอัตโนมัติเพื่อให้ผู้ใช้ไม่ต้องแตะปุ่ม
หากต้องการใช้ฟังก์ชันนี้ คุณต้องมีเหตุการณ์ที่จะทริกเกอร์และสื่อสารไปยัง Fragment จาก ViewModel เมื่อแสดงคำทั้งหมดแล้ว โดยคุณจะใช้LiveDataรูปแบบ Observer เพื่อสร้างโมเดลเหตุการณ์เกมจบ
รูปแบบ Observer
รูปแบบ Observer คือรูปแบบการออกแบบซอฟต์แวร์ โดยจะระบุการสื่อสารระหว่างออบเจ็กต์ ได้แก่ Observable ("Subject" ของการสังเกต) และObserver Observable คือออบเจ็กต์ที่แจ้งให้ Observer ทราบเกี่ยวกับการเปลี่ยนแปลงสถานะของตัวเอง

ในกรณีของ LiveData ในแอปนี้ ออบเจ็กต์ที่สังเกตได้ (Subject) คือออบเจ็กต์ LiveData และ Observer คือเมธอดในตัวควบคุม UI เช่น Fragment การเปลี่ยนแปลงสถานะจะเกิดขึ้นเมื่อใดก็ตามที่ข้อมูลที่อยู่ใน LiveData มีการเปลี่ยนแปลง คลาส LiveData มีความสำคัญอย่างยิ่งในการสื่อสารจาก ViewModel ไปยัง Fragment
ขั้นตอนที่ 1: ใช้ LiveData เพื่อตรวจหาเหตุการณ์เกมจบ
ในงานนี้ คุณจะใช้LiveDataรูปแบบ Observer เพื่อสร้างเหตุการณ์เกมจบ
- ใน
GameViewModelให้สร้างออบเจ็กต์BooleanMutableLiveDataที่ชื่อ_eventGameFinishออบเจ็กต์นี้จะจัดเก็บเหตุการณ์ที่เกมจบแล้ว - หลังจากเริ่มต้นออบเจ็กต์
_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ให้แนบ Observer ไปยังeventGameFinishใช้วิธีobserve()ภายในฟังก์ชัน Lambda ให้เรียกใช้เมธอดgameFinished()
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
if (hasFinished) gameFinished()
})- เรียกใช้แอป เล่นเกม และดูคำทั้งหมด แอปจะนำทางไปยังหน้าจอคะแนนโดยอัตโนมัติ แทนที่จะอยู่ในส่วนเกมจนกว่าคุณจะแตะจบเกม
หลังจากที่รายการคำว่างเปล่า ระบบจะตั้งค่าeventGameFinishเรียกใช้เมธอด Observer ที่เชื่อมโยงในส่วนเกม และแอปจะไปยังส่วนหน้าจอ - โค้ดที่คุณเพิ่มทำให้เกิดปัญหาเกี่ยวกับวงจร หากต้องการทำความเข้าใจปัญหา ให้แสดงความคิดเห็นในโค้ดการนำทางในเมธอด
gameFinished()ในคลาสGameFragmentอย่าลืมเก็บข้อความ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-3 ครั้ง แล้วคุณอาจเห็นข้อความป๊อปอัปทุกครั้ง นี่เป็นข้อบกพร่องเนื่องจากข้อความป๊อปอัปควรแสดงเพียงครั้งเดียวเมื่อเกมจบลง โดยไม่ควรแสดงข้อความป๊อปอัปทุกครั้งที่สร้าง Fragment ใหม่ คุณจะแก้ไขปัญหานี้ได้ในงานถัดไป
|
|
ขั้นตอนที่ 2: รีเซ็ตเหตุการณ์เกมจบ
โดยปกติแล้ว LiveData จะส่งการอัปเดตไปยังผู้สังเกตการณ์เมื่อข้อมูลมีการเปลี่ยนแปลงเท่านั้น ข้อยกเว้นของลักษณะการทำงานนี้คือผู้สังเกตการณ์จะได้รับการอัปเดตด้วยเมื่อเปลี่ยนจากสถานะไม่ได้ใช้งานเป็นสถานะใช้งาน
ด้วยเหตุนี้เอง ระบบจึงทริกเกอร์ข้อความป๊อปอัปว่าเกมจบแล้วซ้ำๆ ในแอปของคุณ เมื่อมีการสร้าง Fragment ของเกมขึ้นมาใหม่หลังจากหมุนหน้าจอ Fragment จะเปลี่ยนจากสถานะที่ไม่ได้ใช้งานเป็นสถานะที่ใช้งานอยู่ ระบบจะเชื่อมต่อ Observer ใน Fragment กับ ViewModel ที่มีอยู่ใหม่ และรับข้อมูลปัจจุบัน ระบบจะเรียกใช้gameFinished()อีกครั้งและแสดงข้อความป๊อปอัป
ในงานนี้ คุณจะแก้ไขปัญหานี้และแสดงข้อความ Toast เพียงครั้งเดียวโดยการรีเซ็ตค่าสถานะ eventGameFinish ใน GameViewModel
- ใน
GameViewModelให้เพิ่มเมธอดonGameFinishComplete()เพื่อรีเซ็ตเหตุการณ์เกมจบ_eventGameFinish
/** Method for the game completed event **/
fun onGameFinishComplete() {
_eventGameFinish.value = false
}- ใน
GameFragmentเมื่อสิ้นสุดgameFinished()ให้เรียกใช้onGameFinishComplete()ในออบเจ็กต์viewModel(ปล่อยให้โค้ดการนำทางในgameFinished()เป็นความคิดเห็นไปก่อน)
private fun gameFinished() {
...
viewModel.onGameFinishComplete()
}- เรียกใช้แอปและเล่นเกม อ่านคำทั้งหมด แล้วเปลี่ยนการวางแนวหน้าจอของอุปกรณ์ โดยข้อความป๊อปอัปจะแสดงเพียงครั้งเดียว
- ใน
GameFragmentภายในเมธอดgameFinished()ให้ยกเลิกการแสดงความคิดเห็นของโค้ดการนำทาง
หากต้องการยกเลิกการแสดงความคิดเห็นใน Android Studio ให้เลือกบรรทัดที่มีการแสดงความคิดเห็น แล้วกดControl+/(Command+/ใน Mac)
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 ไปยังเกม Fragment ว่ารายการคำว่างเปล่า จากนั้นส่วนเกมจะไปยังส่วนคะแนน
ในงานนี้ คุณจะเปลี่ยนคะแนนเป็นออบเจ็กต์ LiveData ใน ScoreViewModel และแนบ Observer ไปกับออบเจ็กต์นั้น งานนี้คล้ายกับสิ่งที่คุณทำเมื่อเพิ่ม LiveData ลงใน GameViewModel
คุณทำการเปลี่ยนแปลงเหล่านี้ใน ScoreViewModel เพื่อให้ข้อมูลทั้งหมดในแอปใช้ LiveData
- ใน
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ให้แนบ Observer สำหรับออบเจ็กต์คะแนน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และ Observer เพื่ออัปเดตคะแนน
ในงานนี้ คุณจะเพิ่มปุ่มเล่นอีกครั้งลงในหน้าจอคะแนนและใช้ Listener การคลิกโดยใช้เหตุการณ์ LiveData ปุ่มนี้จะทริกเกอร์เหตุการณ์เพื่อไปยังหน้าจอเกมจากหน้าจอคะแนน
โค้ดเริ่มต้นสำหรับแอปมีปุ่มเล่นอีกครั้ง แต่ปุ่มจะซ่อนอยู่
- ใน
res/layout/score_fragment.xmlสำหรับปุ่มplay_again_buttonให้เปลี่ยนค่าแอตทริบิวต์visibilityเป็นvisible
<Button
android:id="@+id/play_again_button"
...
android:visibility="visible"
/>- ใน
ScoreViewModelให้เพิ่มออบเจ็กต์LiveDataเพื่อเก็บBooleanที่ชื่อ_eventPlayAgainออบเจ็กต์นี้ใช้เพื่อบันทึกเหตุการณ์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()
}
})นำเข้า androidx.navigation.fragment.findNavController เมื่อ Android Studio แจ้ง
- ใน
ScoreFragmentภายในonCreateView()ให้เพิ่มเครื่องมือฟังการคลิกไปยังปุ่ม PlayAgain แล้วเรียกใช้viewModel.onPlayAgain()
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }- เรียกใช้แอปและเล่นเกม เมื่อเกมจบลง หน้าจอบอกคะแนนจะแสดงคะแนนสุดท้ายและปุ่มเล่นอีกครั้ง แตะปุ่มPlayAgain แล้วแอปจะนำคุณไปยังหน้าจอเกมเพื่อให้เล่นเกมได้อีกครั้ง

เก่งมาก คุณเปลี่ยนสถาปัตยกรรมของแอปเพื่อใช้LiveDataใน ViewModel และแนบ Observer ไปยังออบเจ็กต์ LiveData LiveData จะแจ้งออบเจ็กต์ Observer เมื่อค่าที่ LiveData ถืออยู่มีการเปลี่ยนแปลง
โปรเจ็กต์ Android Studio: GuessTheWord
LiveData
LiveDataคือคลาสที่เก็บข้อมูลที่สังเกตได้ซึ่งรับรู้ถึงวงจรของแอป และเป็นหนึ่งในคอมโพเนนต์สถาปัตยกรรมของ Android- คุณใช้
LiveDataเพื่อเปิดใช้ UI ให้อัปเดตโดยอัตโนมัติเมื่อข้อมูลอัปเดตได้ LiveDataสามารถสังเกตได้ ซึ่งหมายความว่าผู้สังเกตการณ์ เช่น กิจกรรมหรือ Fragment จะได้รับการแจ้งเตือนเมื่อข้อมูลที่ออบเจ็กต์LiveDataเปลี่ยนแปลงLiveDataจัดเก็บข้อมูล ซึ่งเป็น Wrapper ที่ใช้กับข้อมูลใดก็ได้LiveDataตระหนักถึงวงจรของกิจกรรม ซึ่งหมายความว่าจะอัปเดตเฉพาะผู้สังเกตการณ์ที่อยู่ในสถานะวงจรของกิจกรรมที่ใช้งานอยู่ เช่นSTARTEDหรือRESUMED
วิธีเพิ่ม LiveData
- เปลี่ยนประเภทตัวแปรข้อมูลใน
ViewModelเป็นLiveDataหรือMutableLiveData
MutableLiveData คือออบเจ็กต์ LiveData ที่เปลี่ยนค่าได้ MutableLiveData เป็นคลาสทั่วไป ดังนั้นคุณจึงต้องระบุประเภทข้อมูลที่คลาสนี้จัดเก็บ
- หากต้องการเปลี่ยนค่าของข้อมูลที่
LiveDataเก็บไว้ ให้ใช้วิธีsetValue()ในตัวแปรLiveData
หากต้องการแคปซูล LiveData
LiveDataภายในViewModelควรแก้ไขได้ นอกViewModelLiveDataควรอ่านได้ ซึ่งทำได้โดยใช้พร็อพเพอร์ตี้สำรองของ Kotlin- พร็อพเพอร์ตี้สำรองของ Kotlin ช่วยให้คุณส่งคืนสิ่งอื่นจากตัวรับค่าที่ไม่ใช่ออบเจ็กต์ที่แน่นอนได้
- หากต้องการห่อหุ้ม
LiveDataให้ใช้privateMutableLiveDataภายในViewModelและส่งคืนพร็อพเพอร์ตี้การสำรองข้อมูลLiveDataนอกViewModel
LiveData ที่สังเกตได้
LiveDataเป็นไปตามรูปแบบ Observer "Observable" คือLiveDataออบเจ็กต์ และ Observer คือเมธอดในตัวควบคุม UI เช่น Fragment เมื่อใดก็ตามที่ข้อมูลที่ห่อหุ้มอยู่ภายในLiveDataมีการเปลี่ยนแปลง ระบบจะแจ้งให้เมธอด Observer ในตัวควบคุม UI ทราบ- หากต้องการทำให้
LiveDataสังเกตได้ ให้แนบออบเจ็กต์ Observer กับการอ้างอิงLiveDataใน Observer (เช่น กิจกรรมและ Fragment) โดยใช้วิธีobserve() LiveDataรูปแบบ Observer นี้ใช้เพื่อสื่อสารจากViewModelไปยังตัวควบคุม UI ได้
หลักสูตร Udacity:
เอกสารประกอบสำหรับนักพัฒนาแอป Android
อื่นๆ:
- พร็อพเพอร์ตี้สำรองใน Kotlin
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
คุณจะห่อหุ้ม LiveData ที่จัดเก็บไว้ใน ViewModel อย่างไรเพื่อให้ออบเจ็กต์ภายนอกอ่านข้อมูลได้โดยไม่ต้องอัปเดต
- ในออบเจ็กต์
ViewModelให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็นprivateLiveDataใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทMutableLiveData - ในออบเจ็กต์
ViewModelให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็นprivateMutableLiveDataใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทLiveData - ในตัวควบคุม UI ให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็น
privateMutableLiveDataใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทLiveData - ในออบเจ็กต์
ViewModelให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็นLiveDataใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทLiveData
คำถามที่ 2
LiveData จะอัปเดตตัวควบคุม UI (เช่น Fragment) หากตัวควบคุม UI อยู่ในสถานะใดต่อไปนี้
- ทำงานต่อ
- ในเบื้องหลัง
- หยุดชั่วคราว
- หยุดทำงานแล้ว
คำถามที่ 3
ในLiveDataรูปแบบ Observer รายการที่สังเกตได้ (สิ่งที่สังเกต) คืออะไร
- วิธีการสังเกตการณ์
- ข้อมูลในออบเจ็กต์
LiveData - ตัวควบคุม UI
- ออบเจ็กต์
ViewModel
เริ่มบทเรียนถัดไป:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin




