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
เป็น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 คุณเรียกใช้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
และLiveData
Observer
การห่อหุ้มเป็นวิธีจำกัดการเข้าถึงโดยตรงไปยังฟิลด์บางรายการของออบเจ็กต์ เมื่อห่อหุ้มออบเจ็กต์ คุณจะแสดงชุดเมธอดสาธารณะที่แก้ไขฟิลด์ภายในแบบส่วนตัว การใช้การห่อหุ้มช่วยให้คุณควบคุมวิธีที่คลาสอื่นๆ จัดการฟิลด์ภายในเหล่านี้ได้
ในโค้ดปัจจุบัน คลาสภายนอกใดๆ ก็สามารถแก้ไขตัวแปร 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>
- คุณเห็นข้อผิดพลาดในการเริ่มต้น ข้อผิดพลาดนี้เกิดขึ้นเนื่องจากภายใน
GameFragment
score
เป็นการอ้างอิง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
ให้สร้างออบเจ็กต์Boolean
MutableLiveData
ที่ชื่อ_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
ควรแก้ไขได้ นอกViewModel
LiveData
ควรอ่านได้ ซึ่งทำได้โดยใช้พร็อพเพอร์ตี้สำรองของ Kotlin- พร็อพเพอร์ตี้สำรองของ Kotlin ช่วยให้คุณส่งคืนสิ่งอื่นจากตัวรับค่าที่ไม่ใช่ออบเจ็กต์ที่แน่นอนได้
- หากต้องการห่อหุ้ม
LiveData
ให้ใช้private
MutableLiveData
ภายใน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
ให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็นprivate
LiveData
ใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทMutableLiveData
- ในออบเจ็กต์
ViewModel
ให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็นprivate
MutableLiveData
ใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทLiveData
- ในตัวควบคุม UI ให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็น
private
MutableLiveData
ใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทLiveData
- ในออบเจ็กต์
ViewModel
ให้เปลี่ยนประเภทข้อมูลของข้อมูลเป็นLiveData
ใช้พร็อพเพอร์ตี้สำรองเพื่อแสดงข้อมูลแบบอ่านอย่างเดียวของประเภทLiveData
คำถามที่ 2
LiveData
จะอัปเดตตัวควบคุม UI (เช่น Fragment) หากตัวควบคุม UI อยู่ในสถานะใดต่อไปนี้
- ทำงานต่อ
- ในเบื้องหลัง
- หยุดชั่วคราว
- หยุดทำงานแล้ว
คำถามที่ 3
ในLiveData
รูปแบบ Observer รายการที่สังเกตได้ (สิ่งที่สังเกต) คืออะไร
- วิธีการสังเกตการณ์
- ข้อมูลในออบเจ็กต์
LiveData
- ตัวควบคุม UI
- ออบเจ็กต์
ViewModel
เริ่มบทเรียนถัดไป:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin