Lớp học lập trình này thuộc khoá học Kiến thức cơ bản về Kotlin cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tất cả lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.
Giới thiệu
Trong lớp học lập trình trước, bạn đã sử dụng ViewModel
trong ứng dụng Đoán từ để cho phép dữ liệu của ứng dụng vẫn tồn tại sau khi thay đổi cấu hình thiết bị. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách tích hợp LiveData
với dữ liệu trong các lớp ViewModel
. LiveData
(một trong những Bộ thành phần cấu trúc Android) cho phép bạn tạo các đối tượng dữ liệu thông báo cho các khung hiển thị khi cơ sở dữ liệu cơ bản thay đổi.
Để sử dụng lớp LiveData
, bạn thiết lập "trình quan sát" (ví dụ: hoạt động hoặc mảnh) để theo dõi các thay đổi về dữ liệu của ứng dụng. LiveData
nhận biết được vòng đời, nên chỉ cập nhật những đối tượng tiếp nhận dữ liệu thành phần ứng dụng ở trạng thái vòng đời đang hoạt động.
Kiến thức bạn cần có
- Cách tạo các ứng dụng Android cơ bản trong Kotlin.
- Cách điều hướng giữa các đích đến của ứng dụng.
- Vòng đời của hoạt động và mảnh.
- Cách sử dụng các đối tượng
ViewModel
trong ứng dụng. - Cách tạo các đối tượng
ViewModel
bằng giao diệnViewModelProvider.Factory
.
Kiến thức bạn sẽ học được
- Điều gì khiến các đối tượng
LiveData
trở nên hữu ích. - Cách thêm
LiveData
vào dữ liệu được lưu trữ trongViewModel
. - Thời điểm và cách sử dụng
MutableLiveData
. - Cách thêm phương thức tiếp nhận dữ liệu để quan sát các thay đổi trong
LiveData.
- Cách đóng gói
LiveData
bằng thuộc tính sao lưu. - Cách giao tiếp giữa bộ điều khiển giao diện người dùng và
ViewModel
tương ứng.
Bạn sẽ thực hiện
- Sử dụng
LiveData
cho từ và điểm số trong ứng dụng GuessTheWord. - Thêm các đối tượng tiếp nhận dữ liệu để nhận biết khi từ hoặc điểm số thay đổi.
- Cập nhật các khung hiển thị văn bản hiển thị các giá trị đã thay đổi.
- Sử dụng mẫu trình quan sát
LiveData
để thêm sự kiện kết thúc trò chơi. - Triển khai nút Phát lại.
Trong lớp học lập trình Bài 5, bạn sẽ phát triển ứng dụng GuessTheWord, bắt đầu bằng mã khởi động. GuessTheWord là một trò chơi đố chữ dành cho 2 người chơi, trong đó người chơi hợp tác với nhau để đạt điểm cao nhất có thể.
Người chơi thứ nhất nhìn vào các từ trong ứng dụng và lần lượt diễn tả từng từ, nhớ không cho người chơi thứ hai thấy từ đó. Người chơi thứ hai cố gắng đoán từ.
Để chơi trò chơi, người chơi đầu tiên mở ứng dụng trên thiết bị và thấy một từ, chẳng hạn như "guitar", như trong ảnh chụp màn hình bên dưới.
Người chơi đầu tiên diễn tả từ đó, cẩn thận không nói ra từ đó.
- Khi người chơi thứ hai đoán đúng từ, người chơi thứ nhất sẽ nhấn nút Got It (Đã hiểu). Thao tác này sẽ tăng số lượng từ lên một và cho thấy từ tiếp theo.
- Nếu người chơi thứ hai không đoán được từ, người chơi thứ nhất sẽ nhấn vào nút Bỏ qua. Thao tác này sẽ giảm số lượt đoán đi một và chuyển sang từ tiếp theo.
- Để kết thúc trò chơi, hãy nhấn vào nút Kết thúc trò chơi. (Chức năng này không có trong mã khởi đầu của lớp học lập trình đầu tiên trong loạt lớp học lập trình này.)
Trong lớp học lập trình này, bạn sẽ cải thiện ứng dụng Đoán từ bằng cách thêm một sự kiện để kết thúc trò chơi khi người dùng xem hết tất cả các từ trong ứng dụng. Bạn cũng sẽ thêm nút Chơi lại vào mảnh điểm số để người dùng có thể chơi lại trò chơi.
Màn hình tiêu đề | Màn hình trò chơi | Màn hình điểm số |
Trong nhiệm vụ này, bạn sẽ xác định vị trí và chạy mã khởi đầu cho lớp học lập trình này. Bạn có thể dùng ứng dụng GuessTheWord mà bạn đã tạo trong lớp học lập trình trước làm mã khởi đầu hoặc tải một ứng dụng khởi đầu xuống.
- (Không bắt buộc) Nếu không sử dụng mã của mình từ lớp học lập trình trước, hãy tải mã khởi đầu xuống cho lớp học lập trình này. Giải nén mã rồi mở dự án trong Android Studio.
- Chạy ứng dụng và chơi trò chơi.
- Lưu ý rằng nút Skip (Bỏ qua) sẽ chuyển sang từ tiếp theo và giảm điểm đi 1, còn nút Got It (Tôi hiểu) sẽ chuyển sang từ tiếp theo và tăng điểm thêm 1. Nút Kết thúc trò chơi sẽ kết thúc trò chơi.
LiveData
là một lớp lưu giữ dữ liệu có thể quan sát được và nhận biết được vòng đời. Ví dụ: bạn có thể bao bọc LiveData
xung quanh điểm số hiện tại trong ứng dụng Đoán từ. Trong lớp học lập trình này, bạn sẽ tìm hiểu về một số đặc điểm của LiveData
:
LiveData
có thể quan sát được, tức là đối tượng tiếp nhận dữ liệu sẽ nhận được thông báo khi dữ liệu được đối tượngLiveData
giữ có thay đổi.LiveData
giữ dữ liệu;LiveData
là một trình bao bọc có thể được sử dụng với bất kỳ dữ liệu nàoLiveData
nhận biết được vòng đời, nghĩa là chỉ cập nhật những đối tượng tiếp nhận dữ liệu ở trạng thái vòng đời hoạt động, chẳng hạn nhưSTARTED
hoặcRESUMED
.
Trong nhiệm vụ này, bạn sẽ tìm hiểu cách tổng hợp mọi kiểu dữ liệu thành các đối tượng LiveData
bằng cách chuyển đổi dữ liệu điểm số hiện tại và từ hiện tại trong GameViewModel
thành LiveData
. Trong một nhiệm vụ sau, bạn sẽ thêm một đối tượng tiếp nhận dữ liệu vào các đối tượng LiveData
này và tìm hiểu cách quan sát LiveData
.
Bước 1: Thay đổi điểm số và từ để sử dụng LiveData
- Trong gói
screens/game
, hãy mở tệpGameViewModel
. - Thay đổi loại của các biến
score
vàword
thànhMutableLiveData
.MutableLiveData
là mộtLiveData
có thể thay đổi giá trị.MutableLiveData
là một lớp chung, do vậy, bạn cần chỉ định loại dữ liệu mà lớp này giữ.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
- Trong
GameViewModel
, bên trong khốiinit
, hãy khởi chạyscore
vàword
. Để thay đổi giá trị của biếnLiveData
, bạn dùng phương thứcsetValue()
trên biến đó. Trong Kotlin, bạn có thể gọisetValue()
bằng cách sử dụng thuộc tínhvalue
.
init {
word.value = ""
score.value = 0
...
}
Bước 2: Cập nhật tham chiếu đối tượng LiveData
Các biến score
và word
hiện thuộc loại LiveData
. Trong bước này, bạn sẽ thay đổi các giá trị tham chiếu đến những biến này bằng cách sử dụng thuộc tính value
.
- Trong
GameViewModel
, trong phương thứconSkip()
, hãy thay đổiscore
thànhscore.value
. Lưu ý lỗi về việcscore
có thể lànull
. Bạn sẽ khắc phục lỗi này ở phần tiếp theo. - Để giải quyết lỗi này, hãy thêm một chế độ kiểm tra
null
vàoscore.value
trongonSkip()
. Sau đó, hãy gọi hàmminus()
trênscore
, hàm này sẽ thực hiện phép trừ với độ an toànnull
.
fun onSkip() {
if (!wordList.isEmpty()) {
score.value = (score.value)?.minus(1)
}
nextWord()
}
- Cập nhật phương thức
onCorrect()
theo cách tương tự: thêm một chế độ kiểm tranull
vào biếnscore
và sử dụng hàmplus()
.
fun onCorrect() {
if (!wordList.isEmpty()) {
score.value = (score.value)?.plus(1)
}
nextWord()
}
- Trong
GameViewModel
, bên trong phương thứcnextWord()
, hãy thay đổi tham chiếuword
thànhword
.
value
.
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word.value = wordList.removeAt(0)
}
}
- Trong
GameFragment
, bên trong phương thứcupdateWordText()
, hãy thay đổi tham chiếu thànhviewModel
.word
thànhviewModel
.
word
.
value.
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = viewModel.word.value
}
- Trong
GameFragment
, bên trong phương thứcupdateScoreText()
, hãy thay đổi tham chiếu đếnviewModel
.score
thànhviewModel
.
score
.
value.
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.value.toString()
}
- Trong
GameFragment
, bên trong phương thứcgameFinished()
, hãy thay đổi tham chiếu thànhviewModel
.score
thànhviewModel
.
score
.
value
. Thêm lệnh kiểm tra an toànnull
bắt buộc.
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)
}
- Đảm bảo rằng mã của bạn không có lỗi. Biên dịch và chạy ứng dụng. Chức năng của ứng dụng sẽ giống như trước đây.
Nhiệm vụ này có liên quan chặt chẽ đến nhiệm vụ trước, trong đó bạn đã chuyển đổi dữ liệu điểm số và từ thành các đối tượng LiveData
. Trong nhiệm vụ này, bạn sẽ đính kèm các đối tượng Observer
vào các đối tượng LiveData
đó.
- Trong
GameFragment,
bên trong phương thứconCreateView()
, hãy đính kèm một đối tượngObserver
vào đối tượngLiveData
cho điểm số hiện tại,viewModel.score
. Sử dụng phương thứcobserve()
và đặt mã sau khi khởi chạyviewModel
. Sử dụng biểu thức lambda để đơn giản hoá mã. (Biểu thức lambda là một hàm ẩn danh không được khai báo nhưng được chuyển ngay dưới dạng một biểu thức.)
viewModel.score.observe(this, Observer { newScore ->
})
Giải quyết vấn đề trùng lặp tệp đối chiếu với Observer
. Để thực hiện việc này, hãy nhấp vào Observer
, nhấn Alt+Enter
(Option+Enter
trên máy Mac) rồi nhập androidx.lifecycle.Observer
.
- Đối tượng tiếp nhận dữ liệu mà bạn vừa tạo sẽ nhận được một sự kiện khi dữ liệu do đối tượng
LiveData
được quan sát giữ có thay đổi. Bên trong trình quan sát, hãy cập nhật điểm sốTextView
bằng điểm số mới.
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Đính kèm một đối tượng
Observer
vào đối tượngLiveData
của từ hiện tại. Làm theo cách tương tự như cách bạn đính kèm một đối tượngObserver
vào điểm số hiện tại.
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})
Khi giá trị của score
hoặc word
thay đổi, score
hoặc word
hiển thị trên màn hình sẽ tự động cập nhật.
- Trong
GameFragment
, hãy xoá các phương thứcupdateWordText()
vàupdateScoreText()
, cũng như tất cả các tệp tham chiếu đến các phương thức đó. Bạn không cần đến các đối tượng tiếp nhận dữ liệu đó nữa, vì các phương thức của đối tượng tiếp nhận dữ liệuLiveData
sẽ cập nhật khung hiển thị văn bản. - Chạy ứng dụng. Ứng dụng trò chơi của bạn sẽ hoạt động chính xác như trước, nhưng giờ đây, ứng dụng sẽ sử dụng các đối tượng tiếp nhận dữ liệu
LiveData
vàLiveData
.
Đóng gói là một cách để hạn chế quyền truy cập trực tiếp vào một số trường của một đối tượng. Khi đóng gói một đối tượng, bạn cho thấy một tập hợp phương thức công khai có chức năng sửa đổi các trường nội bộ riêng tư. Bằng cách đóng gói, bạn có thể kiểm soát cách các lớp khác điều khiển những trường nội bộ này.
Trong mã hiện tại, mọi lớp bên ngoài đều có thể sửa đổi các biến score
và word
bằng cách sử dụng thuộc tính value
, chẳng hạn như sử dụng viewModel.score.value
. Điều này có thể không quan trọng trong ứng dụng mà bạn đang phát triển trong lớp học lập trình này, nhưng trong một ứng dụng phát hành công khai, bạn muốn kiểm soát dữ liệu trong các đối tượng ViewModel
.
Chỉ ViewModel
mới được phép chỉnh sửa dữ liệu trong ứng dụng của bạn. Tuy nhiên, các trình điều khiển giao diện người dùng cần đọc dữ liệu, vì vậy, các trường dữ liệu không thể hoàn toàn riêng tư. Để đóng gói dữ liệu của ứng dụng, bạn sẽ dùng cả đối tượng MutableLiveData
và LiveData
.
MutableLiveData
đấu với LiveData
:
- Bạn có thể thay đổi dữ liệu trong đối tượng
MutableLiveData
, như tên cho thấy. Bên trongViewModel
, dữ liệu phải có thể chỉnh sửa được, vậy nên dữ liệu sẽ sử dụngMutableLiveData
. - Bạn có thể đọc dữ liệu trong đối tượng
LiveData
nhưng không thể thay đổi. Bên ngoàiViewModel
, dữ liệu phải có thể đọc được nhưng không thể chỉnh sửa được, vậy nên dữ liệu phải xuất hiện dưới dạngLiveData
.
Để thực hiện chiến lược này, bạn sẽ sử dụng một thuộc tính sao lưu của Kotlin. Thuộc tính sao lưu cho phép bạn trả về nội dung qua một phương thức getter khác với đối tượng chính xác. Trong nhiệm vụ này, bạn sẽ triển khai một thuộc tính hỗ trợ cho các đối tượng score
và word
trong ứng dụng Đoán từ.
Thêm một thuộc tính sao lưu vào điểm số và từ
- Trong
GameViewModel
, hãy tạo đối tượngscore
hiện tạiprivate
. - Để tuân theo quy ước đặt tên được dùng trong các thuộc tính sao lưu, hãy thay đổi
score
thành_score
. Thuộc tính_score
hiện là phiên bản có thể thay đổi của điểm số trò chơi, được dùng nội bộ. - Tạo một phiên bản công khai của loại
LiveData
, có tên làscore
.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
- Bạn thấy lỗi khởi chạy. Lỗi này xảy ra vì bên trong
GameFragment
,score
là một tham chiếuLiveData
vàscore
không thể truy cập vào phương thức thiết lập của nó nữa. Để tìm hiểu thêm về các phương thức getter và setter trong Kotlin, hãy xem bài viết Getters and Setters (Các phương thức getter và setter).
Để giải quyết lỗi này, hãy ghi đè phương thứcget()
cho đối tượngscore
trongGameViewModel
và trả về thuộc tính hỗ trợ_score
.
val score: LiveData<Int>
get() = _score
- Trong
GameViewModel
, hãy thay đổi các tham chiếu củascore
thành phiên bản có thể thay đổi nội bộ của nó,_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)
}
...
}
- Đổi tên đối tượng
word
thành_word
và thêm một thuộc tính sao lưu cho đối tượng đó, như bạn đã làm cho đối tượngscore
.
// 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)
}
}
Tuyệt vời, bạn đã đóng gói các đối tượng LiveData
word
và score
.
Ứng dụng hiện tại của bạn sẽ chuyển đến màn hình điểm số khi người dùng nhấn vào nút End Game (Kết thúc trò chơi). Bạn cũng muốn ứng dụng chuyển đến màn hình điểm số khi người chơi đã chơi hết tất cả các từ. Sau khi người chơi hoàn thành từ cuối cùng, bạn muốn trò chơi tự động kết thúc để người dùng không phải nhấn vào nút.
Để triển khai chức năng này, bạn cần có một sự kiện được kích hoạt và truyền đến mảnh từ ViewModel
khi tất cả các từ đã xuất hiện. Để làm việc này, bạn sẽ sử dụng mẫu trình quan sát LiveData
để mô hình hoá một sự kiện kết thúc trò chơi.
Mẫu đối tượng tiếp nhận dữ liệu
Mẫu đối tượng tiếp nhận dữ liệu là một mẫu thiết kế phần mềm. Mẫu này chỉ định hoạt động kết nối giữa các đối tượng: một đối tượng phát ra dữ liệu ("chủ thể" quan sát) và đối tượng tiếp nhận dữ liệu. Đối tượng phát ra dữ liệu là một đối tượng thông báo cho các đối tượng tiếp nhận dữ liệu về những thay đổi liên quan đến trạng thái của nó.
Trong trường hợp LiveData
trong ứng dụng này, đối tượng có thể quan sát (đối tượng) là đối tượng LiveData
và các đối tượng quan sát là các phương thức trong đơn vị điều khiển giao diện người dùng, chẳng hạn như các mảnh. Trạng thái thay đổi mỗi khi dữ liệu được bao bọc bên trong LiveData
thay đổi. Các lớp LiveData
rất quan trọng trong việc kết nối giữa ViewModel
với mảnh.
Bước 1: Sử dụng LiveData để phát hiện sự kiện kết thúc trò chơi
Trong nhiệm vụ này, bạn sẽ sử dụng mẫu trình quan sát LiveData
để mô hình hoá một sự kiện kết thúc trò chơi.
- Trong
GameViewModel
, hãy tạo một đối tượngBoolean
MutableLiveData
có tên là_eventGameFinish
. Đối tượng này sẽ lưu giữ sự kiện kết thúc trò chơi. - Sau khi khởi động đối tượng
_eventGameFinish
, hãy tạo và khởi động một thuộc tính hỗ trợ có tên làeventGameFinish
.
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
get() = _eventGameFinish
- Trong
GameViewModel
, hãy thêm phương thứconGameFinish()
. Trong phương thức này, hãy đặt sự kiện kết thúc trò chơieventGameFinish
thànhtrue
.
/** Method for the game completed event **/
fun onGameFinish() {
_eventGameFinish.value = true
}
- Trong
GameViewModel
, bên trong phương thứcnextWord()
, hãy kết thúc trò chơi nếu danh sách từ trống.
private fun nextWord() {
if (wordList.isEmpty()) {
onGameFinish()
} else {
//Select and remove a _word from the list
_word.value = wordList.removeAt(0)
}
}
- Trong
GameFragment
, bên trongonCreateView()
, sau khi khởi chạyviewModel
, hãy đính kèm một đối tượng tiếp nhận dữ liệu vàoeventGameFinish
. Sử dụng phương thứcobserve()
. Bên trong hàm lambda, hãy gọi phương thứcgameFinished()
.
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
if (hasFinished) gameFinished()
})
- Chạy ứng dụng, chơi trò chơi và xem hết tất cả các từ. Ứng dụng sẽ tự động chuyển đến màn hình điểm số thay vì ở trong đoạn mã trò chơi cho đến khi bạn nhấn vào Kết thúc trò chơi.
Sau khi danh sách từ trống,eventGameFinish
được đặt, phương thức trình quan sát được liên kết trong mảnh trò chơi sẽ được gọi và ứng dụng sẽ chuyển đến mảnh màn hình. - Mã bạn đã thêm gây ra vấn đề về vòng đời. Để hiểu rõ vấn đề, trong lớp
GameFragment
, hãy đánh dấu ghi chú vào mã điều hướng trong phương thứcgameFinished()
. Hãy nhớ giữ thông báoToast
trong phương thức này.
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)
}
- Chạy ứng dụng, chơi trò chơi và xem hết tất cả các từ. Một thông báo ngắn cho biết "Trận đấu vừa kết thúc" sẽ xuất hiện trong thời gian ngắn ở cuối màn hình trận đấu. Đây là hành vi dự kiến.
Bây giờ, hãy xoay thiết bị hoặc trình mô phỏng. Thông báo sẽ xuất hiện lại! Xoay thiết bị thêm vài lần nữa, có thể bạn sẽ thấy thông báo mỗi lần. Đây là một lỗi, vì thông báo chỉ được hiển thị một lần, khi trò chơi kết thúc. Thông báo tạm thời không được hiển thị mỗi khi phân mảnh được tạo lại. Bạn sẽ giải quyết vấn đề này trong nhiệm vụ tiếp theo.
Bước 2: Đặt lại sự kiện kết thúc trò chơi
Thông thường, LiveData
chỉ phân phối thông tin cập nhật cho các trình quan sát khi dữ liệu thay đổi. Hành vi này có một ngoại lệ là trình quan sát cũng sẽ nhận được thông tin cập nhật khi thay đổi trạng thái từ không hoạt động sang đang hoạt động.
Đây là lý do thông báo dạng toast về việc hoàn thành trò chơi được kích hoạt nhiều lần trong ứng dụng của bạn. Khi mảnh trò chơi được tạo lại sau khi xoay màn hình, mảnh này sẽ chuyển từ trạng thái không hoạt động sang trạng thái hoạt động. Đối tượng theo dõi trong mảnh được kết nối lại với ViewModel
hiện có và nhận được dữ liệu hiện tại. Phương thức gameFinished()
được kích hoạt lại và thông báo hiển thị.
Trong nhiệm vụ này, bạn sẽ khắc phục vấn đề này và chỉ hiển thị thông báo một lần bằng cách đặt lại cờ eventGameFinish
trong GameViewModel
.
- Trong
GameViewModel
, hãy thêm phương thứconGameFinishComplete()
để đặt lại sự kiện kết thúc trò chơi,_eventGameFinish
.
/** Method for the game completed event **/
fun onGameFinishComplete() {
_eventGameFinish.value = false
}
- Trong
GameFragment
, khi kết thúcgameFinished()
, hãy gọionGameFinishComplete()
trên đối tượngviewModel
. (Tạm thời để mã điều hướng tronggameFinished()
ở trạng thái có nhận xét.)
private fun gameFinished() {
...
viewModel.onGameFinishComplete()
}
- Chạy ứng dụng và chơi trò chơi. Đọc hết tất cả các từ, sau đó thay đổi hướng màn hình của thiết bị. Thông báo chỉ xuất hiện một lần.
- Trong
GameFragment
, bên trong phương thứcgameFinished()
, hãy bỏ chú thích mã điều hướng.
Để bỏ chú thích trong Android Studio, hãy chọn các dòng được chú thích rồi nhấnControl+/
(Command+/
trên máy 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()
}
Nếu Android Studio nhắc, hãy nhập androidx.navigation.fragment.NavHostFragment.findNavController
.
- Chạy ứng dụng và chơi trò chơi. Đảm bảo rằng ứng dụng tự động chuyển đến màn hình điểm số cuối cùng sau khi bạn chơi xong tất cả các từ.
Tuyệt vời! Ứng dụng của bạn sử dụng LiveData
để kích hoạt sự kiện kết thúc trò chơi nhằm giao tiếp từ GameViewModel
đến mảnh trò chơi rằng danh sách từ trống. Sau đó, phân mảnh trò chơi sẽ chuyển đến phân mảnh điểm số.
Trong nhiệm vụ này, bạn sẽ thay đổi điểm số thành một đối tượng LiveData
trong ScoreViewModel
và đính kèm một đối tượng tiếp nhận dữ liệu vào đối tượng đó. Nhiệm vụ này tương tự như những gì bạn đã làm khi thêm LiveData
vào GameViewModel
.
Bạn thực hiện những thay đổi này đối với ScoreViewModel
để đảm bảo tính đầy đủ, sao cho tất cả dữ liệu trong ứng dụng của bạn đều sử dụng LiveData
.
- Trong
ScoreViewModel
, hãy thay đổi loại biếnscore
thànhMutableLiveData
. Đổi tên theo quy ước thành_score
và thêm một thuộc tính sao lưu.
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
get() = _score
- Trong
ScoreViewModel
, bên trong khốiinit
, hãy khởi chạy_score
. Bạn có thể xoá hoặc giữ lại nhật ký trong khốiinit
tuỳ ý.
init {
_score.value = finalScore
}
- Trong
ScoreFragment
, bên trongonCreateView()
, sau khi khởi chạyviewModel
, hãy đính kèm một đối tượng tiếp nhận dữ liệu cho đối tượng điểm sốLiveData
. Bên trong biểu thức lambda, hãy đặt giá trị điểm thành chế độ xem văn bản điểm. Xoá mã trực tiếp chỉ định chế độ xem văn bản bằng giá trị điểm số khỏiViewModel
.
Mã cần thêm:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
Đoạn mã cần xoá:
binding.scoreText.text = viewModel.score.toString()
Khi được Android Studio nhắc, hãy nhập androidx.lifecycle.Observer
.
- Chạy ứng dụng và chơi trò chơi. Ứng dụng sẽ hoạt động như trước, nhưng giờ đây, ứng dụng sẽ sử dụng
LiveData
và một đối tượng tiếp nhận dữ liệu để cập nhật điểm số.
Trong nhiệm vụ này, bạn sẽ thêm nút Play Again (Chơi lại) vào màn hình điểm số và triển khai trình nghe lượt nhấp của nút này bằng sự kiện LiveData
. Nút này kích hoạt một sự kiện để chuyển từ màn hình điểm số sang màn hình trò chơi.
Đoạn mã khởi đầu cho ứng dụng có nút Play Again (Phát lại), nhưng nút này bị ẩn.
- Trong
res/layout/score_fragment.xml
, đối với nútplay_again_button
, hãy thay đổi giá trị của thuộc tínhvisibility
thànhvisible
.
<Button
android:id="@+id/play_again_button"
...
android:visibility="visible"
/>
- Trong
ScoreViewModel
, hãy thêm một đối tượngLiveData
để lưu giữ mộtBoolean
có tên là_eventPlayAgain
. Đối tượng này dùng để lưu sự kiệnLiveData
nhằm chuyển từ màn hình điểm số sang màn hình trò chơi.
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
get() = _eventPlayAgain
- Trong
ScoreViewModel
, hãy xác định các phương thức để đặt và đặt lại sự kiện,_eventPlayAgain
.
fun onPlayAgain() {
_eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
_eventPlayAgain.value = false
}
- Trong
ScoreFragment
, hãy thêm một đối tượng tiếp nhận dữ liệu choeventPlayAgain
. Đặt mã ở cuốionCreateView()
, trước câu lệnhreturn
. Bên trong biểu thức lambda, hãy quay lại màn hình trò chơi và đặt lạieventPlayAgain
.
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
if (playAgain) {
findNavController().navigate(ScoreFragmentDirections.actionRestart())
viewModel.onPlayAgainComplete()
}
})
Nhập androidx.navigation.fragment.findNavController
khi được Android Studio nhắc.
- Trong
ScoreFragment
, bên trongonCreateView()
, hãy thêm một trình nghe lượt nhấp vào nút PlayAgain (Phát lại) và gọiviewModel
.onPlayAgain()
.
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- Chạy ứng dụng và chơi trò chơi. Khi trò chơi kết thúc, màn hình điểm số sẽ cho thấy điểm số cuối cùng và nút Chơi lại. Nhấn vào nút PlayAgain (Chơi lại) và ứng dụng sẽ chuyển đến màn hình trò chơi để bạn có thể chơi lại.
Chúc mừng bạn! Bạn đã thay đổi cấu trúc của ứng dụng để dùng các đối tượng LiveData
trong ViewModel
và bạn đã đính kèm các đối tượng theo dõi vào các đối tượng LiveData
. LiveData
thông báo cho các đối tượng tiếp nhận dữ liệu khi giá trị do LiveData
giữ thay đổi.
Dự án Android Studio: GuessTheWord
LiveData
LiveData
là một lớp lưu giữ dữ liệu có thể quan sát được và nhận biết được vòng đời, một trong những Thành phần cấu trúc Android.- Bạn có thể dùng
LiveData
để cho phép giao diện người dùng tự động cập nhật khi dữ liệu cập nhật. LiveData
có thể quan sát được, tức là một đối tượng tiếp nhận dữ liệu (chẳng hạn như một hoạt động hoặc một mảnh) có thể nhận được thông báo khi dữ liệu do đối tượngLiveData
giữ có thay đổi.LiveData
giữ dữ liệu; đây là một trình bao bọc có thể được dùng với mọi dữ liệu.LiveData
nhận biết được vòng đời, nghĩa là chỉ cập nhật những đối tượng tiếp nhận dữ liệu ở trạng thái vòng đời hoạt động, chẳng hạn nhưSTARTED
hoặcRESUMED
.
Cách thêm LiveData
- Thay đổi loại biến dữ liệu trong
ViewModel
thànhLiveData
hoặcMutableLiveData
.
MutableLiveData
là một đối tượng LiveData
có thể thay đổi giá trị. MutableLiveData
là một lớp chung, do vậy, bạn cần chỉ định loại dữ liệu mà lớp này giữ.
- Để thay đổi giá trị của dữ liệu do
LiveData
nắm giữ, hãy dùng phương thứcsetValue()
trên biếnLiveData
.
Để đóng gói LiveData
- Bạn có thể chỉnh sửa
LiveData
bên trongViewModel
. Bên ngoàiViewModel
,LiveData
phải đọc được. Bạn có thể triển khai việc này bằng cách sử dụng một thuộc tính sao lưu của Kotlin. - Thuộc tính sao lưu Kotlin cho phép bạn trả về nội dung qua một phương thức getter khác với đối tượng chính xác.
- Để đóng gói
LiveData
, hãy dùngprivate
MutableLiveData
bên trongViewModel
và trả về một thuộc tính hỗ trợLiveData
bên ngoàiViewModel
.
LiveData có thể quan sát
LiveData
tuân theo mẫu của đối tượng tiếp nhận dữ liệu. "Đối tượng có thể quan sát" là đối tượngLiveData
và các đối tượng quan sát là các phương thức trong đơn vị điều khiển giao diện người dùng, chẳng hạn như các mảnh. Mỗi khi dữ liệu được bao bọc bên trongLiveData
thay đổi, các phương thức tiếp nhận dữ liệu trong trình điều khiển giao diện người dùng sẽ nhận được thông báo.- Để có thể quan sát
LiveData
, hãy đính kèm một đối tượng tiếp nhận dữ liệu vào tham chiếuLiveData
trong các đối tượng tiếp nhận dữ liệu (chẳng hạn như hoạt động và mảnh) bằng phương thứcobserve()
. - Bạn có thể dùng mẫu đối tượng tiếp nhận dữ liệu
LiveData
này để giao tiếp từViewModel
đến các bộ điều khiển giao diện người dùng.
Khoá học của Udacity:
Tài liệu dành cho nhà phát triển Android:
Khác:
- Thuộc tính sao lưu trong Kotlin
Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:
- Giao bài tập về nhà nếu cần.
- Trao đổi với học viên về cách nộp bài tập về nhà.
- Chấm điểm bài tập về nhà.
Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.
Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.
Trả lời các câu hỏi sau
Câu hỏi 1
Bạn đóng gói LiveData
lưu trữ trong ViewModel
như thế nào để các đối tượng bên ngoài có thể đọc nhưng không thể cập nhật dữ liệu?
- Bên trong đối tượng
ViewModel
, hãy thay đổi loại của dữ liệu thànhprivate
LiveData
. Sử dụng thuộc tính sao lưu để hiển thị dữ liệu chỉ có thể đọc thuộc loạiMutableLiveData
. - Bên trong đối tượng
ViewModel
, hãy thay đổi loại của dữ liệu thànhprivate
MutableLiveData
. Sử dụng thuộc tính sao lưu để hiển thị dữ liệu chỉ có thể đọc thuộc loạiLiveData
. - Bên trong trình điều khiển giao diện người dùng, hãy thay đổi loại của dữ liệu thành
private
MutableLiveData
. Sử dụng thuộc tính sao lưu để hiển thị dữ liệu chỉ có thể đọc thuộc loạiLiveData
. - Bên trong đối tượng
ViewModel
, hãy thay đổi loại của dữ liệu thànhLiveData
. Sử dụng thuộc tính sao lưu để hiển thị dữ liệu chỉ có thể đọc thuộc loạiLiveData
.
Câu hỏi 2
LiveData
sẽ cập nhật trình điều khiển giao diện người dùng (chẳng hạn như một mảnh) nếu trình này ở một trong các trạng thái sau đây?
- Đã tiếp tục
- Trong chế độ nền
- Đã tạm dừng
- Đã dừng
Câu hỏi 3
Trong mẫu quan sát LiveData
, mục quan sát (mục được quan sát) là gì?
- Phương thức quan sát
- Dữ liệu trong đối tượng
LiveData
- Trình điều khiển giao diện người dùng
- Đối tượng
ViewModel
Bắt đầu bài học tiếp theo:
Để biết đường liên kết đến các lớp học lập trình khác trong khoá học này, hãy xem trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.