Kiến thức cơ bản về Kotlin cho Android 05.2: LiveData và trình quan sát LiveData

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ện ViewModelProvider.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ữ trong ViewModel.
  • 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.

  1. (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.
  2. Chạy ứng dụng và chơi trò chơi.
  3. 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ượng LiveData 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ào
  • 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ặc RESUMED.

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

  1. Trong gói screens/game, hãy mở tệp GameViewModel.
  2. Thay đổi loại của các biến scoreword thành MutableLiveData.

    MutableLiveData là một 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ữ.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
  1. Trong GameViewModel, bên trong khối init, hãy khởi chạy scoreword. Để thay đổi giá trị của biến LiveData, bạn dùng phương thức setValue() trên biến đó. Trong Kotlin, bạn có thể gọi setValue() bằng cách sử dụng thuộc tính value.
init {

   word.value = ""
   score.value = 0
  ...
}

Bước 2: Cập nhật tham chiếu đối tượng LiveData

Các biến scoreword 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.

  1. Trong GameViewModel, trong phương thức onSkip(), hãy thay đổi score thành score.value. Lưu ý lỗi về việc score có thể là null. Bạn sẽ khắc phục lỗi này ở phần tiếp theo.
  2. Để giải quyết lỗi này, hãy thêm một chế độ kiểm tra null vào score.value trong onSkip(). Sau đó, hãy gọi hàm minus() trên score, hàm này sẽ thực hiện phép trừ với độ an toàn null.
fun onSkip() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.minus(1)
   }
   nextWord()
}
  1. Cập nhật phương thức onCorrect() theo cách tương tự: thêm một chế độ kiểm tra null vào biến score và sử dụng hàm plus().
fun onCorrect() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.plus(1)
   }
   nextWord()
}
  1. Trong GameViewModel, bên trong phương thức nextWord(), hãy thay đổi tham chiếu word thành word.value.
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       word.value = wordList.removeAt(0)
   }
}
  1. Trong GameFragment, bên trong phương thức updateWordText(), hãy thay đổi tham chiếu thành viewModel.word thành viewModel.word.value.
/** Methods for updating the UI **/
private fun updateWordText() {
   binding.wordText.text = viewModel.word.value
}
  1. Trong GameFragment, bên trong phương thức updateScoreText(), hãy thay đổi tham chiếu đến viewModel.score thành viewModel.score.value.
private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.value.toString()
}
  1. Trong GameFragment, bên trong phương thức gameFinished(), hãy thay đổi tham chiếu thành viewModel.score thành viewModel.score.value. Thêm lệnh kiểm tra an toàn null 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)
}
  1. Đả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 đó.

  1. Trong GameFragment, bên trong phương thức onCreateView(), hãy đính kèm một đối tượng Observer vào đối tượng LiveData cho điểm số hiện tại, viewModel.score. Sử dụng phương thức observe() và đặt mã sau khi khởi chạy viewModel. 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.

  1. Đố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()
})
  1. Đính kèm một đối tượng Observer vào đối tượng LiveData 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ượng Observer 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.

  1. Trong GameFragment, hãy xoá các phương thức updateWordText()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ệu LiveData sẽ cập nhật khung hiển thị văn bản.
  2. 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 LiveDataLiveData.

Đó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 scoreword 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 MutableLiveDataLiveData.

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 trong ViewModel, dữ liệu phải có thể chỉnh sửa được, vậy nên dữ liệu sẽ sử dụng MutableLiveData.
  • Bạn có thể đọc dữ liệu trong đối tượng LiveData nhưng không thể thay đổi. Bên ngoài ViewModel, 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ạng LiveData.

Để 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 scoreword 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ừ

  1. Trong GameViewModel, hãy tạo đối tượng score hiện tại private.
  2. Để 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ộ.
  3. 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>
  1. 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ếu LiveDatascore 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ức get() cho đối tượng score trong GameViewModel và trả về thuộc tính hỗ trợ _score.
val score: LiveData<Int>
   get() = _score
  1. Trong GameViewModel, hãy thay đổi các tham chiếu của score 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)
   }
   ...
}
  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ượng 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)
   }
}

Tuyệt vời, bạn đã đóng gói các đối tượng LiveData wordscore.

Ứ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.

  1. Trong GameViewModel, hãy tạo một đối tượng Boolean 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.
  2. 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
  1. Trong GameViewModel, hãy thêm phương thức onGameFinish(). Trong phương thức này, hãy đặt sự kiện kết thúc trò chơi eventGameFinish thành true.
/** Method for the game completed event **/
fun onGameFinish() {
   _eventGameFinish.value = true
}
  1. Trong GameViewModel, bên trong phương thức nextWord(), 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)
   }
}
  1. Trong GameFragment, bên trong onCreateView(), sau khi khởi chạy viewModel, hãy đính kèm một đối tượng tiếp nhận dữ liệu vào eventGameFinish. Sử dụng phương thức observe(). Bên trong hàm lambda, hãy gọi phương thức gameFinished().
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
   if (hasFinished) gameFinished()
})
  1. 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.
  2. 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ức gameFinished(). Hãy nhớ giữ thông báo Toast 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)
   }
  1. 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.

  1. Trong GameViewModel, hãy thêm phương thức onGameFinishComplete() để đặ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
}
  1. Trong GameFragment, khi kết thúc gameFinished(), hãy gọi onGameFinishComplete() trên đối tượng viewModel. (Tạm thời để mã điều hướng trong gameFinished() ở trạng thái có nhận xét.)
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. 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.
  2. Trong GameFragment, bên trong phương thức gameFinished(), 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ấn Control+/ (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.

  1. 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.

  1. Trong ScoreViewModel, hãy thay đổi loại biến score thành MutableLiveData. Đổ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
  1. Trong ScoreViewModel, bên trong khối init, hãy khởi chạy _score. Bạn có thể xoá hoặc giữ lại nhật ký trong khối init tuỳ ý.
init {
   _score.value = finalScore
}
  1. Trong ScoreFragment, bên trong onCreateView(), sau khi khởi chạy viewModel, 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ỏi ViewModel.

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.

  1. 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.

  1. Trong res/layout/score_fragment.xml, đối với nút play_again_button, hãy thay đổi giá trị của thuộc tính visibility thành visible.
<Button
   android:id="@+id/play_again_button"
...
   android:visibility="visible"
 />
  1. Trong ScoreViewModel, hãy thêm một đối tượng LiveData để lưu giữ một Boolean có tên là _eventPlayAgain. Đối tượng này dùng để lưu sự kiện LiveData 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
  1. 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
}
  1. Trong ScoreFragment, hãy thêm một đối tượng tiếp nhận dữ liệu cho eventPlayAgain. Đặt mã ở cuối onCreateView(), trước câu lệnh return. Bên trong biểu thức lambda, hãy quay lại màn hình trò chơi và đặt lại eventPlayAgain.
// 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.

  1. Trong ScoreFragment, bên trong onCreateView(), hãy thêm một trình nghe lượt nhấp vào nút PlayAgain (Phát lại) và gọi viewModel.onPlayAgain().
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. 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ượng LiveData 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ặc RESUMED.

Cách thêm LiveData

  • Thay đổi loại biến dữ liệu trong ViewModel thành LiveData hoặc MutableLiveData.

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ức setValue() trên biến LiveData.

Để đóng gói LiveData

  • Bạn có thể chỉnh sửa LiveData bên trong ViewModel. Bên ngoài ViewModel, 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ùng private MutableLiveData bên trong ViewModel và trả về một thuộc tính hỗ trợ LiveData bên ngoài ViewModel.

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ượ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. Mỗi khi dữ liệu được bao bọc bên trong LiveData 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ếu LiveData 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ức observe().
  • 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:

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ành private LiveData. Sử dụng thuộc tính sao lưu để hiển thị dữ liệu chỉ có thể đọc thuộc loại MutableLiveData.
  • Bên trong đối tượng ViewModel, 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ại LiveData.
  • 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ại LiveData.
  • Bên trong đối tượng ViewModel, hãy thay đổi loại của dữ liệu thành LiveData. Sử dụng thuộc tính sao lưu để hiển thị dữ liệu chỉ có thể đọc thuộc loại LiveData.

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: 5.3: Liên kết dữ liệu với ViewModel và LiveData

Để 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.