Codelab นี้เป็นส่วนหนึ่งของหลักสูตรพื้นฐานเกี่ยวกับ Kotlin ใน Android คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากทํางานผ่าน Codelab ตามลําดับ Codelab ของหลักสูตรทั้งหมดจะแสดงอยู่ในหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals
ข้อมูลเบื้องต้น
ใน Codelab ที่ผ่านมา คุณใช้ ViewModel
ในแอป GuessTheWord เพื่ออนุญาตให้ข้อมูลของแอปอยู่รอดจากการเปลี่ยนแปลงการกําหนดค่าอุปกรณ์ ใน Codelab นี้ คุณเรียนรู้วิธีผสานรวม LiveData
กับข้อมูลในชั้นเรียน ViewModel
LiveData
ซึ่งเป็น 1 ในคอมโพเนนต์สถาปัตยกรรมของ Android ช่วยให้คุณสร้างออบเจ็กต์ข้อมูลที่จะแจ้งให้ดูเมื่อฐานข้อมูลพื้นฐานมีการเปลี่ยนแปลง
หากต้องการใช้คลาส LiveData
ให้ตั้งค่า "observers" (เช่น กิจกรรมหรือส่วนย่อย) ที่สังเกตการเปลี่ยนแปลงในข้อมูลของแอป LiveData
รู้จักวงจรนี้ จึงอัปเดตเฉพาะผู้สังเกตการณ์คอมโพเนนต์ของแอปที่อยู่ในสถานะอายุการใช้งานที่ใช้งานอยู่
สิ่งที่ควรทราบอยู่แล้ว
- วิธีสร้างแอป Android ขั้นพื้นฐานใน Kotlin
- วิธีไปยังส่วนต่างๆ ของแอปปลายทาง
- กิจกรรมและวงจรของส่วนย่อย
- วิธีใช้ออบเจ็กต์
ViewModel
ในแอป - วิธีสร้างออบเจ็กต์
ViewModel
โดยใช้อินเทอร์เฟซViewModelProvider.Factory
สิ่งที่คุณจะได้เรียนรู้
- สิ่งที่ทําให้ออบเจ็กต์
LiveData
มีประโยชน์ - วิธีเพิ่ม
LiveData
ลงในข้อมูลที่เก็บไว้ในViewModel
- ควรใช้
MutableLiveData
เมื่อใดและอย่างไร - วิธีเพิ่มวิธีการสังเกตเห็นการเปลี่ยนแปลงใน
LiveData.
- วิธีใส่
LiveData
โดยใช้พร็อพเพอร์ตี้การสํารองข้อมูล - วิธีสื่อสารระหว่างตัวควบคุม UI และ
ViewModel
ที่เกี่ยวข้อง
สิ่งที่คุณจะทํา
- ใช้
LiveData
สําหรับคําและคะแนนในแอป GuessTheWord - เพิ่มผู้สังเกตการณ์ที่สังเกตว่าคําหรือคะแนนมีการเปลี่ยนแปลง
- อัปเดตมุมมองข้อความที่แสดงค่าที่มีการเปลี่ยนแปลง
- ใช้รูปแบบการสังเกต
LiveData
เพื่อเพิ่มเหตุการณ์ที่เกมจบไปแล้ว - ใช้ปุ่มเล่นอีกครั้ง
ใน Codelab บทที่ 5 คุณจะพัฒนาแอป GuessTheWord ได้โดยเริ่มต้นด้วยโค้ดเริ่มต้น GuessTheWord เป็นเกมสไตล์ชาร์ดแบบผู้เล่น 2 คน ซึ่งผู้เล่นจะทํางานร่วมกันเพื่อให้ได้คะแนนสูงสุดเท่าที่จะเป็นไปได้
ผู้เล่นคนแรกจะดูคําในแอปและกระทําทีละคํา และอย่าแสดงคํานั้นให้ผู้เล่นคนที่สองเห็น ผู้เล่นรายที่ 2 พยายามเดาคํานั้น
ในการเล่นเกม ผู้เล่นคนแรกจะเปิดแอปในอุปกรณ์และจะเห็นคํา เช่น "guitar," ดังที่แสดงในภาพหน้าจอด้านล่าง
ผู้เล่นคนแรกจะทําตามคําและระวังอย่าพูดคํานั้นจริงๆ
- เมื่อผู้เล่นคนที่ 2 เดาคําได้อย่างถูกต้อง ผู้เล่นคนแรกจะกดปุ่มรับทราบ ซึ่งจะเพิ่มจํานวนเป็น 1 และแสดงคําถัดไป
- หากผู้เล่นรายที่ 2 เดาคําไม่ได้ ผู้เล่นคนแรกจะกดปุ่มข้าม ซึ่งจะลดจํานวนลงและข้ามไปยังคําถัดไป
- หากต้องการจบเกม ให้กดปุ่มจบเกม (ฟังก์ชันนี้จะไม่อยู่ในโค้ดเริ่มต้นสําหรับ Codelab แรกในชุด)
ใน Codelab นี้ คุณสามารถปรับปรุงแอป GuessTheWord ได้โดยเพิ่มเหตุการณ์เพื่อจบเกมเมื่อผู้ใช้เลื่อนดูคําทั้งหมดในแอป และยังเพิ่มปุ่มเล่นอีกครั้งในส่วนคะแนนเพื่อให้ผู้ใช้เล่นเกมได้อีกครั้ง
หน้าจอชื่อ |
หน้าจอเกม |
หน้าจอคะแนน |
ในงานนี้ คุณจะค้นหาและเรียกใช้โค้ดเริ่มต้นสําหรับ Codelab นี้ คุณจะใช้แอป GuessTheWord ที่สร้างไว้ใน Codelab ก่อนหน้าเป็นโค้ดเริ่มต้น หรือจะดาวน์โหลดแอปเริ่มต้นก็ได้
- (ไม่บังคับ) หากคุณไม่ได้ใช้โค้ดจาก Codelab ก่อนหน้า ให้ดาวน์โหลดโค้ดเริ่มต้นสําหรับ Codelab นี้ แตกไฟล์โค้ดแล้วเปิดโปรเจ็กต์ใน Android Studio
- เรียกใช้แอปและเล่นเกม
- โปรดสังเกตว่าปุ่มข้ามจะแสดงคําถัดไปและลดคะแนนลง 1 รายการ และปุ่มรับทราบจะแสดงคําถัดไปและเพิ่มคะแนนเป็น 1 คะแนน ปุ่มสิ้นสุดเกมจะจบเกม
LiveData
เป็นคลาสผู้ถือข้อมูลที่สังเกตการณ์ได้และรับรู้ถึงวงจรการใช้งาน เช่น คุณจะรวม LiveData
รอบๆ คะแนนปัจจุบันในแอป GuessTheWord ได้ ใน Codelab นี้ คุณจะได้รู้ลักษณะเฉพาะต่างๆ ของ LiveData
ดังนี้
LiveData
สังเกตได้ ซึ่งหมายความว่าผู้สังเกตการณ์จะได้รับการแจ้งเตือนเมื่อข้อมูลที่ออบเจ็กต์LiveData
ระงับไว้มีการเปลี่ยนแปลงLiveData
เก็บรักษาข้อมูล WrapperLiveData
ใช้ได้กับข้อมูลใดก็ได้LiveData
มีไว้สําหรับวงจรการใช้งาน ซึ่งหมายความว่าจะอัปเดตเฉพาะผู้สังเกตการณ์ที่มีสถานะอยู่ในสถานะใช้งานอยู่ เช่นSTARTED
หรือRESUMED
เท่านั้น
ในบทเรียนนี้ คุณจะได้เรียนรู้วิธีรวมข้อมูลทุกประเภทไว้ในออบเจ็กต์ LiveData
โดยการแปลงคะแนนปัจจุบันและข้อมูลคําปัจจุบันใน GameViewModel
เป็น LiveData
ในงานถัดไป คุณต้องเพิ่มผู้สังเกตการณ์ลงในออบเจ็กต์ 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
ให้เปลี่ยนเมธอดscore
เป็นscore.value
ในเมธอดonSkip()
สังเกตข้อผิดพลาดเกี่ยวกับscore
ที่อาจเป็นnull
คุณจะแก้ไขข้อผิดพลาดถัดไปได้ - หากต้องการแก้ไขข้อผิดพลาด ให้เพิ่มการตรวจสอบ
null
ลงในscore.value
ในonSkip()
จากนั้นเรียกใช้ฟังก์ชันminus()
ในscore
ซึ่งจะดําเนินการลบด้วยความปลอดภัยของnull
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
ให้เปลี่ยนการอ้างอิงword
เป็นเมธอดword
word
เป็นword
.
value
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word.value = wordList.removeAt(0)
}
}
- ใน
GameFragment
ให้เปลี่ยนการอ้างอิงเป็นviewModel
ภายในเมธอดupdateWordText()
เป็น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
ให้เปลี่ยนการอ้างอิงเป็นviewModel
ภายในเมธอดgameFinished()
เป็น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
- ผู้สังเกตที่คุณเพิ่งสร้างจะได้รับเหตุการณ์เมื่อข้อมูลที่ออบเจ็กต์
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
Encapsulation คือวิธีจํากัดการเข้าถึงบางช่องของออบเจ็กต์โดยตรง เมื่อรวมออบเจ็กต์ไว้ คุณจะเปิดเผยชุดวิธีการสาธารณะที่แก้ไขช่องภายในส่วนตัวได้ การใช้สภาพแวดล้อมช่วยให้คุณควบคุมได้ว่าจะให้ชั้นเรียนอื่นๆ จัดการช่องภายในเหล่านี้อย่างไร
ในโค้ดปัจจุบัน ชั้นเรียนภายนอกจะแก้ไขตัวแปร score
และ word
ได้โดยใช้พร็อพเพอร์ตี้ value
เช่น โดยใช้ viewModel.score.value
ซึ่งอาจจะไม่สําคัญในแอปที่คุณกําลังพัฒนาใน Codelab นี้ แต่ในแอปเวอร์ชันที่ใช้งานจริง คุณต้องการควบคุมข้อมูลในออบเจ็กต์ ViewModel
เฉพาะ ViewModel
เท่านั้นที่ควรแก้ไขข้อมูลในแอป แต่ตัวควบคุม UI ต้องอ่านข้อมูล ดังนั้นช่องข้อมูลจึงไม่สามารถเป็นส่วนตัวโดยสิ้นเชิงได้ ในการสรุปข้อมูลแอป คุณจะใช้ทั้งออบเจ็กต์ MutableLiveData
และ LiveData
ได้
MutableLiveData
เทียบกับ LiveData
:
- ข้อมูลใน
MutableLiveData
อาจเปลี่ยนแปลงได้ตามชื่อที่ระบุ ภายในViewModel
ข้อมูลควรแก้ไขได้ เพื่อให้ใช้MutableLiveData
อยู่ - อ่านข้อมูลในออบเจ็กต์
LiveData
ได้ แต่จะเปลี่ยนแปลงไม่ได้ เมื่ออยู่นอกViewModel
ข้อมูลต้องอ่านได้ แต่แก้ไขไม่ได้ จึงควรแสดงข้อมูลเป็นLiveData
หากต้องการสร้างกลยุทธ์นี้ ให้ใช้พร็อพเพอร์ตี้การสํารองข้อมูลของ Kotlin พร็อพเพอร์ตี้สํารองช่วยให้คุณส่งคืนบางอย่างจาก Getter ที่ไม่ใช่ออบเจ็กต์ที่ตรงกันทุกประการได้ ในงานนี้ คุณจะได้ใช้พร็อพเพอร์ตี้การสํารองข้อมูลสําหรับออบเจ็กต์ 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
ไม่สามารถเข้าถึงตัวตั้งค่าได้อีกต่อไป หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับ Getter และ Setter ใน Kotlin โปรดดู Getters และ Setters
หากต้องการแก้ไขข้อผิดพลาด ให้ลบล้างเมธอด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
แล้ว
แอปปัจจุบันไปยังหน้าจอคะแนนเมื่อผู้ใช้แตะปุ่มจบเกม คุณต้องการให้แอปไปยังหน้าจอคะแนนเมื่อผู้เล่นปั่นจักรยานผ่านคําทั้งหมด หลังจากที่ผู้เล่นจบที่คําสุดท้ายแล้ว คุณต้องการให้เกมสิ้นสุดลงโดยอัตโนมัติเพื่อให้ผู้ใช้ไม่ต้องแตะปุ่มดังกล่าว
ในการใช้งานฟังก์ชันนี้ คุณต้องมีเหตุการณ์ที่จะทริกเกอร์และสื่อสารกับส่วนย่อยจาก ViewModel
เมื่อคําทั้งหมดแสดงขึ้น โดยใช้รูปแบบการสังเกตการณ์ LiveData
เพื่อจําลองเหตุการณ์ที่เกมเสร็จสิ้น
รูปแบบผู้สังเกตการณ์
รูปแบบเซิร์ฟเวอร์โฆษณาคือรูปแบบการออกแบบซอฟต์แวร์ ซึ่งจะระบุการสื่อสารระหว่างออบเจ็กต์ต่างๆ ได้แก่ สังเกตได้ ("subject" จากการสังเกตการณ์) และการสังเกตการณ์ สิ่งที่สังเกตได้คือออบเจ็กต์ที่สังเกตเห็นผู้สังเกตการณ์เกี่ยวกับการเปลี่ยนแปลงสถานะ
ในกรณีที่เป็น LiveData
ในแอปนี้ สิ่งที่สังเกตได้ (เรื่อง) คือวัตถุ LiveData
และการสังเกตการณ์คือวิธีการในตัวควบคุม UI เช่น ส่วนย่อย การเปลี่ยนแปลงสถานะจะเกิดขึ้นเมื่อข้อมูลที่อยู่ใน LiveData
มีการเปลี่ยนแปลง คลาส LiveData
มีความสําคัญในการสื่อสารจาก ViewModel
ไปยังส่วนย่อย
ขั้นตอนที่ 1: ใช้ LiveData เพื่อตรวจหาเหตุการณ์ที่เสร็จสิ้นในเกม
ในงานนี้ คุณจะใช้รูปแบบการสังเกตการณ์ LiveData
เพื่อจําลองเหตุการณ์ที่เกมเสร็จสิ้น
- ใน
GameViewModel
ให้สร้างออบเจ็กต์MutableLiveData
ของBoolean
ชื่อ_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
ให้แนบผู้สังเกตการณ์กับeventGameFinish
ใช้เมธอดobserve()
เรียกเมธอดgameFinished()
ในฟังก์ชันแลมบ์ดา
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
if (hasFinished) gameFinished()
})
- ลงแอป เล่นเกม และอ่านให้ครบทุกคํา แอปจะนําทางไปยังหน้าจอคะแนนโดยอัตโนมัติ แทนที่จะเป็นส่วนย่อยของเกมจนกว่าคุณจะแตะจบเกม
หลังจากลิสต์คําว่างเปล่าแล้วeventGameFinish
จะตั้งค่าแล้ว ระบบจะเรียกใช้เมธอดผู้สังเกตที่เกี่ยวข้องใน Fragment เกม และแอปจะไปยังส่วน Fragment ของหน้าจอ - โค้ดที่คุณเพิ่มทําให้เกิดปัญหาด้านอายุการใช้งาน หากต้องการทําความเข้าใจปัญหา ให้แสดงความคิดเห็นเกี่ยวกับโค้ดนําทางในเมธอด
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 ครั้ง และคุณอาจเห็นข้อความโทสต์ทุกครั้ง กรณีนี้เป็นข้อบกพร่อง เนื่องจากข้อความโทสต์ควรแสดงเพียงครั้งเดียวเมื่อเกมเล่นจบ ข้อความโทสต์ไม่ควรแสดงทุกครั้งที่มีการสร้างส่วนย่อยใหม่ คุณแก้ไขปัญหานี้ได้ในงานถัดไป
ขั้นตอนที่ 2: รีเซ็ตเหตุการณ์การเล่นเกมที่จบแล้ว
โดยปกติ LiveData
จะส่งอัปเดตให้ผู้สังเกตการณ์ก็ต่อเมื่อมีการเปลี่ยนแปลงข้อมูลเท่านั้น ทั้งนี้ ลักษณะการทํางานเช่นนี้เป็นเพียงข้อสังเกตด้วยเช่นกันว่าผู้สังเกตการณ์จะยังได้รับการอัปเดตเมื่อผู้สังเกตการณ์เปลี่ยนสถานะเป็นไม่มีการใช้งาน
นี่เป็นสาเหตุที่ข้อความโทสต์ที่เล่นเกมจบไปแล้วมีการทริกเกอร์ซ้ําในแอปของคุณ เมื่อมีการสร้างส่วนย่อยของเกมอีกครั้งหลังจากการหมุนหน้าจอ ข้อความโทสต์จะเปลี่ยนจากสถานะที่ไม่ได้ใช้งานเป็นสถานะใช้งานอยู่ ผู้สังเกตในส่วนย่อยจะเชื่อมต่อกับ ViewModel
ที่มีอยู่อีกครั้งและรับข้อมูลปัจจุบัน ระบบจะเรียกเมธอด gameFinished()
อีกครั้งและแสดงโทสต์
ในงานนี้ คุณจะแก้ไขปัญหานี้และแสดงข้อความโทสต์เพียงครั้งเดียว โดยรีเซ็ตแฟล็ก 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 ของเกมจะไปยังส่วน Fragment คะแนน
ในงานนี้ คุณจะได้เปลี่ยนคะแนนเป็นออบเจ็กต์ LiveData
ใน ScoreViewModel
และแนบการสังเกตการณ์ไปที่ออบเจ็กต์นั้น งานนี้คล้ายกับงานที่คุณเพิ่ม 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
ให้แนบผู้สังเกตการณ์สําหรับคะแนนLiveData
กําหนดค่านิพจน์เป็นมุมมองข้อความคะแนนในนิพจน์แลมบ์ดา นําโค้ดที่กําหนดมุมมองข้อความออกด้วยค่าคะแนนออกจาก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
และผู้สังเกตการณ์เพื่ออัปเดตคะแนน
ในงานนี้ คุณจะได้เพิ่มปุ่มเล่นอีกครั้งในหน้าจอคะแนน แล้วนํา Listener การคลิกไปใช้โดยใช้เหตุการณ์ LiveData
ปุ่มนี้จะทริกเกอร์เหตุการณ์เพื่อไปยังหน้าจอคะแนนเพื่อไปยังหน้าจอเกม
โค้ดเริ่มต้นสําหรับแอปมีปุ่มเล่นอีกครั้ง แต่ปุ่มถูกซ่อนอยู่
- ใน
res/layout/score_fragment.xml
สําหรับปุ่มplay_again_button
ให้เปลี่ยนแอตทริบิวต์visibility
's เป็น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
ภายในนิพจน์แลมบ์ดา ให้กลับไปที่หน้าจอเกมและรีเซ็ต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()
ให้เพิ่ม Listener การคลิกลงในปุ่ม PlayAgain แล้วเรียกviewModel
.onPlayAgain()
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- เรียกใช้แอปและเล่นเกม เมื่อเกมเล่นจบ หน้าจอคะแนนจะแสดงคะแนนสุดท้ายและปุ่มเล่นอีกครั้ง แตะปุ่ม PlayAgain แล้วแอปจะไปยังหน้าจอเกมเพื่อเล่นเกมอีกครั้ง
เก่งมาก! คุณเปลี่ยนสถาปัตยกรรมของแอปเพื่อใช้LiveData
ออบเจ็กต์ใน ViewModel
และได้แนบการสังเกตการณ์กับออบเจ็กต์ LiveData
LiveData
จะแจ้งเตือนวัตถุของผู้สังเกตเมื่อค่าที่ LiveData
ระงับไว้มีการเปลี่ยนแปลง
โปรเจ็กต์ Android Studio: GuessTheWord
LiveData
LiveData
เป็นคลาสผู้ถือข้อมูลที่สังเกตการณ์ได้และรับรู้เกี่ยวกับวงจรชีวิตซึ่งเป็นหนึ่งในคอมโพเนนต์สถาปัตยกรรมของ Android- คุณใช้
LiveData
เพื่ออัปเดต UI ได้โดยอัตโนมัติเมื่ออัปเดตข้อมูล LiveData
สามารถสังเกตได้ ซึ่งหมายความว่าผู้สังเกตการณ์ เช่น กิจกรรมหรือส่วนย่อยอาจได้รับการแจ้งเตือนเมื่อข้อมูลที่คงโดยออบเจ็กต์LiveData
เปลี่ยนแปลงLiveData
จะเก็บข้อมูลไว้ และเป็น Wrapper ที่ใช้กับข้อมูลใดก็ได้LiveData
มีไว้สําหรับวงจรการใช้งาน ซึ่งหมายความว่าจะอัปเดตเฉพาะผู้สังเกตการณ์ที่มีสถานะอยู่ในสถานะใช้งานอยู่ เช่นSTARTED
หรือRESUMED
เท่านั้น
วิธีเพิ่ม LiveData
- เปลี่ยนประเภทตัวแปรข้อมูลใน
ViewModel
เป็นLiveData
หรือMutableLiveData
MutableLiveData
เป็นออบเจ็กต์ LiveData
ซึ่งเปลี่ยนแปลงค่าได้ MutableLiveData
เป็นคลาสทั่วไป คุณจึงต้องระบุประเภทของข้อมูลที่เก็บ
- หากต้องการเปลี่ยนค่าของข้อมูลที่
LiveData
เก็บไว้ ให้ใช้เมธอดsetValue()
ในตัวแปรLiveData
เพื่อปกปิด LiveData
LiveData
ภายในViewModel
ควรแก้ไขได้ ภายนอกViewModel
ควรอ่านLiveData
ได้ ซึ่งทําได้โดยใช้พร็อพเพอร์ตี้การสํารองข้อมูลของ Kotlin- คุณสมบัติในการสํารองข้อมูลของ Kotlin ช่วยให้คุณส่งคืนบางสิ่งจาก Getter นอกเหนือจากวัตถุจริงได้
- ในการสรุป
LiveData
ให้ใช้private
MutableLiveData
ภายในViewModel
และแสดงผลพร็อพเพอร์ตี้สนับสนุนLiveData
ภายนอกViewModel
LiveData ที่ตรวจพบได้
LiveData
เป็นไปตามรูปแบบของผู้สังเกตการณ์ "observable" เป็นออบเจ็กต์LiveData
และการสังเกตการณ์คือเมธอดในตัวควบคุม UI เช่น Fragment เมื่อใดก็ตามที่ข้อมูลที่อยู่ในLiveData
มีการเปลี่ยนแปลง วิธีการสังเกตการณ์ในตัวควบคุม UI จะได้รับการแจ้งเตือน- หากต้องการทําให้
LiveData
สังเกตได้ ให้แนบออบเจ็กต์การสังเกตการณ์เข้ากับการอ้างอิงLiveData
ในการสังเกตการณ์ (เช่น กิจกรรมและส่วนย่อย) โดยใช้เมธอดobserve()
- รูปแบบการสังเกตการณ์
LiveData
นี้ใช้เพื่อสื่อสารจาก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 (เช่น ส่วนย่อย) หากตัวควบคุม UI อยู่ในสถานะใด
- กลับมาทำงานอีกครั้ง
- เล่นขณะล็อกหน้าจอ
- หยุดชั่วคราว
- หยุดทำงานแล้ว
คำถามที่ 3
ในรูปแบบผู้สังเกตการณ์ LiveData
มีสิ่งใดที่สังเกตได้ (สิ่งที่สังเกตได้)
- วิธีการสังเกตการณ์
- ข้อมูลในออบเจ็กต์
LiveData
- ตัวควบคุม UI
- ออบเจ็กต์
ViewModel
เริ่มบทเรียนถัดไป:
สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals