Android Kotlin Fundamentals 05.2: LiveData và những đối tượng tiếp nhận dữ liệu của LiveData

Lớp học lập trình này nằm trong khóa học về Khái niệm cơ bản về Android Kotlin. Bạn sẽ nhận được nhiều giá trị nhất từ khóa học này nếu bạn làm việc qua các lớp học lập trình theo trình tự. Tất cả các lớp học lập trình trong khóa học đều có trên trang đích của các lớp học lập trình cơ bản về Android Kotlin.

Giới thiệu

Trong lớp học lập trình trước đây, bạn đã sử dụng ViewModel trong ứng dụng GuessTheWord để cho phép dữ liệu của ứng dụng tồn tại trong các thay đổi về 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 Thành phần cấu trúc Android, cho phép bạn xây dựng các đối tượng dữ liệu để thông báo cho chế độ xem khi cơ sở dữ liệu cơ bản thay đổi.

Để dùng lớp LiveData, bạn thiết lập "observers" (ví dụ: các hoạt động hoặc mảnh) quan sát những thay đổi trong dữ liệu của ứng dụng. LiveData nhận biết được vòng đời, do đó, tính năng này chỉ cập nhật những đối tượng tiếp nhận dữ liệu thành phần ứng dụng đang ở 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 di chuyển giữa các đích của ứng dụng.
  • Vòng đời của hoạt động và mảnh.
  • Cách sử dụng đối tượng ViewModel trong ứng dụng của bạn.
  • Cách tạo đố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 đố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 thức 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 một thuộc tính hỗ trợ.
  • Cách kết nối giữa bộ điều khiển giao diện người dùng và ViewModel tương ứng của bộ điều khiển.

Bạn sẽ thực hiện

  • Sử dụng LiveData cho từ này và điểm số trong ứng dụng GuessTheWord.
  • Thêm đối tượng tiếp nhận dữ liệu để ý đến khi từ hoặc điểm số thay đổi.
  • Cập nhật các chế độ xem văn bản hiển thị các giá trị đã thay đổi.
  • Sử dụng mẫu đối tượng tiếp nhận dữ liệu LiveData để thêm một sự kiện đã hoàn thành trò chơi.
  • Triển khai nút Phát lại.

Trong các lớp học lập trình của Bài học 5, bạn phát triển ứng dụng GuessTheWord, bắt đầu bằng mã dành cho người mới bắt đầu. GuessTheWord là trò chơi theo phong cách charades Hai người chơi, trong đó người chơi cộng tác để đạt được điểm số cao nhất có thể.

Người chơi đầu tiên nhìn vào các từ trong ứng dụng và lần lượt thực hiện từng từ, đảm bảo không hiển thị từ đó cho người chơi thứ hai. 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ừ, ví dụ như "guitar," như minh họa trong ảnh chụp màn hình dưới đây.

Người chơi đầu tiên thực hiện từ này, cẩn thận để không thực sự nói từ đó.

  • Khi người chơi thứ hai đoán đúng từ, người chơi đầu tiên nhấn nút Tôi hiểu để tăng số lượng và tính từ tiếp theo.
  • Nếu người chơi thứ hai không thể đoán từ, người chơi đầu tiên sẽ nhấn nút Bỏ qua để giảm một từ và bỏ qua từ tiếp theo.
  • Để kết thúc trò chơi, hãy nhấn nút Kết thúc trò chơi. (Chức năng này không có trong mã bắt đầu cho lớp học lập trình đầu tiên trong chuỗi.)

Trong lớp học lập trình này, bạn cải thiện ứng dụng GuessTheWord bằng cách thêm một sự kiện để kết thúc trò chơi khi người dùng chuyển qua tất cả các từ trong ứng dụng. Bạn cũng có thể thêm nút Chơi lại trong mảnh điểm để 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

Trong nhiệm vụ này, bạn xác định vị trí và chạy mã khởi động cho lớp học lập trình này. Bạn có thể sử dụng ứng dụng GuessTheWord mà bạn đã tích hợp trong lớp học lập trình trước đó làm mã dành cho người mới bắt đầu hoặc bạn có thể tải ứng dụng dành cho người mới bắt đầu xuống.

  1. (Không bắt buộc) Nếu bạn không sử dụng mã trong lớp học lập trình trước đó, hãy tải mã dành cho người mới bắt đầu xuống trong 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 Bỏ qua hiển thị từ tiếp theo và giảm điểm số một, đồng thời nút Tôi hiểu hiển thị từ tiếp theo và tăng điểm số một. 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 gồm một LiveData xung quanh điểm số hiện tại trong ứng dụng GuessTheWord. Trong lớp học lập trình này, bạn 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 đang ở trạng thái vòng đời đang hoạt động như STARTED hoặc RESUMED.

Trong nhiệm vụ này, bạn sẽ tìm hiểu cách thêm bất kỳ loại dữ liệu nào vào đối tượng LiveData bằng cách chuyển đổi điểm hiện tại và dữ liệu từ hiện tại trong GameViewModel thành LiveData. Trong một nhiệm vụ sau đó, bạn 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 biến scoreword thành MutableLiveData.

    MutableLiveData là một LiveData có giá trị có thể thay đổi được. MutableLiveData là lớp chung, do đó, bạn cần chỉ định loại dữ liệu mà lớp đó lưu 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 thuộc tính value.
init {

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

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

Biến scoreword hiện thuộc loại LiveData. Ở bước này, bạn thay đổi các thông tin tham chiếu đến các 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. Xin lưu ý rằng lỗi về score có thể là null. Bạn sẽ khắc phục lỗi này tiếp theo.
  2. Để giải quyết lỗi này, hãy thêm một dấu kiểm null vào score.value trong onSkip(). Sau đó, gọi hàm minus() trên score để thực hiện phép trừ bằng null-safety.
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 dấu kiểm 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 tệp đối 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 tệp đối 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 tệp đối chiếu thành 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 tệp đối chiếu thành viewModel.score thành viewModel.score.value. Thêm tính năng 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 không có lỗi trong mã của bạn. Biên dịch và chạy ứng dụng của bạn. Chức năng của ứng dụng phải giống như trước đây.

Việc cần làm này có liên quan chặt chẽ với việc cần làm trước đó, trong đó bạn đã chuyển đổi điểm số và dữ liệu từ thành các đối tượng LiveData. Trong việc cần làm này, bạn đính kèm đố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 hiện tại, viewModel.score. Hãy sử dụng phương thức observe() và đặt mã sau khi khởi chạy viewModel. Hãy dùng một biểu thức lambda để đơn giản hóa mã. (Biểu thức lambda là hàm ẩn danh không được khai báo nhưng được chuyển ngay dưới dạng biểu thức.)
viewModel.score.observe(this, Observer { newScore ->
})

Giải quyết tệp đối chiếu thành Observer. Để làm điều 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 nhận được một sự kiện khi dữ liệu do đối tượng LiveData quan sát được thay đổi. Bên trong đối tượng tiếp nhận dữ liệu, 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 từ hiện tại LiveData. Làm tương tự như cách bạn đã đính kèm đố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 xuất hiện trên màn hình sẽ tự động cập nhật.

  1. Trong GameFragment, hãy xóa 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 sử dụng nữa vì các chế độ xem văn bản được cập nhật bằng phương thức quan sát LiveData.
  2. Chạy ứng dụng của bạn. Ứng dụng trò chơi của bạn sẽ hoạt động giống như trước đây, nhưng giờ đây, ứng dụng sẽ sử dụng LiveDataLiveData đối tượng tiếp nhận dữ liệu.

Đó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 đố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 của bạn, 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, ví dụ như bằng cách sử dụng viewModel.score.value. Quy trình này có thể không quan trọng trong ứng dụng mà bạn đang phát triển, nhưng trong ứng dụng chính thức, bạn cần kiểm soát dữ liệu trong các đối tượng ViewModel.

Chỉ ViewModel mới chỉnh sửa được dữ liệu trong ứng dụng của bạn. Tuy nhiên, bộ điều khiển giao diện người dùng cần phải đọ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 so với LiveData:

  • Bạn có thể thay đổi dữ liệu trong đối tượng MutableLiveData, như được ngụ ý trong tên. Bên trong ViewModel, dữ liệu phải được chỉnh sửa để sử dụng MutableLiveData.
  • Bạn có thể đọc nhưng không thể thay đổi dữ liệu trong đối tượng LiveData. Từ bên ngoài ViewModel, dữ liệu phải dễ đọc nhưng không thể chỉnh sửa, vì vậy dữ liệu phải hiển thị dưới dạng LiveData.

Để triển khai chiến lược này, bạn hãy sử dụng thuộc tính dự phòng 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 triển khai thuộc tính hỗ trợ cho các đối tượng scoreword trong ứng dụng GuessTheWord.

Thêm một thuộc tính hỗ trợ vào điểm số và từ

  1. Trong GameViewModel, hãy đặt đối tượng score hiện tại thành private.
  2. Để tuân theo quy ước đặt tên dùng trong các thuộc tính dự phòng, 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ố trong trò chơi để sử dụng nội bộ.
  3. Tạo một phiên bản công khai của loại LiveData, được gọi là score.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
  1. Bạn gặp lỗi khởi chạy. Lỗi này xảy ra vì trong GameFragment, score là tệp tham chiếu LiveDatascore không thể truy cập vào phương thức setter nữa. Để tìm hiểu thêm về phương thức getter và phương thức setter trong Kotlin, hãy xem bài viết Các phương thức getter và setter.

    Để giải quyết lỗi, hãy ghi đè phương thức get() cho đối tượng score trong GameViewModel và trả về thuộc tính sao lưu, _score.
val score: LiveData<Int>
   get() = _score
  1. Trong GameViewModel, hãy thay đổi các tệp tham chiếu của score thành phiên bản có thể thay đổi nội bộ của _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. Hãy đổi tên đối tượng word thành _word và thêm thuộc tính hỗ trợ cho đối tượng này, như đối với đố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)
   }
}

Thật tuyệt, bạn đã đóng gói các đối tượng LiveData wordscore.

Ứng dụng hiện tại sẽ chuyển đến màn hình điểm khi người dùng nhấn vào nút 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 khi người chơi di chuyển qua tất cả các từ. Sau khi người chơi kết thúc bằng từ cuối cùng, bạn muốn trò chơi kết thúc tự động để 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 một sự kiện được kích hoạt và thông báo cho mảnh từ ViewModel khi tất cả các từ đã được hiển thị. Để làm điều này, bạn sử dụng mẫu đối tượng tiếp nhận dữ liệu LiveData để lập mô hình 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. Lệnh này chỉ định việc giao tiếp giữa các đối tượng: đố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. Một đố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ó.

Đối với LiveData trong ứng dụng này, đối tượng phát ra dữ liệu (chủ thể) là đối tượng LiveData, còn đối tượng tiếp nhận dữ liệu là các phương thức trong bộ điều khiển giao diện người dùng, chẳng hạn như các mảnh. Trạng thái sẽ thay đổi bất cứ khi nào dữ liệu được gói bên trong LiveData thay đổi. Các lớp LiveData có vai trò 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 một sự kiện đã kết thúc trò chơi

Trong nhiệm vụ này, bạn dùng mẫu quan sát LiveData để lập mô hình 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 đã hoàn thành trò chơi.
  2. Sau khi khởi tạo đối tượng _eventGameFinish, hãy tạo và khởi tạo một thuộc tính sao lưu 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, hãy đặt sự kiện đã hoàn thành 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 tạo 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à khám phá tất cả các từ. Ứng dụng sẽ tự động chuyển đến màn hình điểm, thay vì ở trong mảnh 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 thiết lập, phương thức quan sát 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 đã dẫn đến một vấn đề về vòng đời. Để hiểu vấn đề, trong lớp GameFragment, hãy nhận xét mã điều hướng trong phương thức gameFinished(). Hãy đảm bảo giữ lại thông báo Toast trong phương thứ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. Chạy ứng dụng, chơi trò chơi và khám phá tất cả các từ. Một thông báo ngắn có nội dung "Trò chơi vừa kết thúc" sẽ xuất hiện nhanh ở cuối màn hình trò chơi, đây là hành vi dự kiến.

Bây giờ, hãy xoay thiết bị hoặc trình mô phỏng. Chào mừng bạn! Xoay thiết bị vài lần nữa và có thể bạn sẽ luôn thấy thông báo nhanh. Đây là một lỗi vì thông báo nhanh chỉ hiển thị một lần khi trò chơi kết thúc. Bánh mì nướng sẽ không hiển thị mỗi khi mảnh được tạo lại. Bạn giải quyết vấn đề này trong nhiệm vụ tiếp theo.

Bước 2: Đặt lại sự kiện đã hoàn thành trò chơi

Thông thường, LiveData sẽ chỉ cập nhật cho đối tượng tiếp nhận dữ liệu khi dữ liệu thay đổi. Một trường hợp ngoại lệ đối với hành vi này là các đối tượng tiếp nhận dữ liệu cũng nhận được các bản cập nhật khi đối tượng tiếp nhận dữ liệu thay đổi từ trạng thái không hoạt động sang trạng thái đang hoạt động.

Đây là lý do tại sao bánh mì nướng đã hoàn tất 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 sẽ chuyển từ trạng thái không hoạt động sang trạng thái đang hoạt động. Trình quan sát trong mảnh được kết nối lại với ViewModel hiện có và nhận dữ liệu hiện tại. Phương thức gameFinished() được kích hoạt lại và thông báo nhanh sẽ hiển thị.

Trong tác vụ này, bạn khắc phục sự cố này và chỉ hiển thị thông báo nhanh 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, vào cuối gameFinished(), hãy gọi onGameFinishComplete() trên đối tượng viewModel. (Còn bây giờ, hãy để lại mã điều hướng trong gameFinished() được nhận xét.)
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. Chạy ứng dụng và chơi trò chơi. Xem qua tất cả các từ, sau đó thay đổi hướng màn hình của thiết bị. Bánh mì nướng chỉ hiển thị một lần.
  2. Trong GameFragment, ở bên trong phương thức gameFinished(), hãy hủy nhận xét mã điều hướng.

    Để hủy nhận xét trong Android Studio, hãy chọn các dòng được nhận xét và nhấn vào biểu tượng 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. Hãy đảm bảo rằng ứng dụng tự động chuyển đến màn hình điểm cuối cùng sau khi bạn xem hết tất cả các từ.

Thật tuyệt! Ứng dụng của bạn dùng LiveData để kích hoạt một sự kiện đã hoàn thành trò chơi để kết nối từ GameViewModel đến mảnh trò chơi mà danh sách từ trống. Sau đó, mảnh trò chơi chuyển đến mảnh điểm.

Trong nhiệm vụ này, bạn 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 đó. Việc cần làm này tương tự như việc 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 hoàn chỉnh, nhờ đó, tất cả dữ liệu trong ứng dụng của bạn 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 rồi thêm một thuộc tính hỗ trợ.
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ể xóa hoặc để lại nhật ký trong khối init theo ý muốn.
init {
   _score.value = finalScore
}
  1. Trong ScoreFragment, bên trong onCreateView(), sau khi khởi tạo 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. Xóa mã chỉ định trực tiếp chế độ xem văn bản bằng giá trị điểm khỏi ViewModel.

Mã cần thêm:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})

Mã cần xóa:

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 của bạn và chơi trò chơi. Ứng dụng sẽ hoạt động như trước đây, nhưng giờ đây, ứng dụng 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 thêm nút Phát lại vào màn hình điểm số và triển khai trình xử lý lượt nhấp bằng cách sử dụng sự kiện LiveData. Nút này sẽ kích hoạt một sự kiện để chuyển từ màn hình điểm sang màn hình trò chơi.

Mã khởi động cho ứng dụng bao gồm nút 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 đối tượng LiveData để lưu giữ Boolean có tên _eventPlayAgain. Đối tượng này được dùng để lưu sự kiện LiveData để chuyển từ màn hình điểm đến 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 xử lý lượt nhấp vào nút PlayAgain và gọi viewModel.onPlayAgain().
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Chạy ứng dụng của bạn và chơi trò chơi. Khi trò chơi kết thúc, màn hình điểm sẽ hiển thị điểm cuối cùng và nút Chơi lại. Nhấn vào nút PlayAgain rồi ứng dụng đó chuyển đến màn hình trò chơi để bạn có thể chơi lại trò chơi.

Rất tốt! Bạn đã thay đổi cấu trúc của ứng dụng để sử dụng các đối tượng LiveData trong ViewModel và đã đính kèm đối tượng tiếp nhận dữ liệu vào đối tượng LiveData. LiveData sẽ thông báo cho các đối tượng tiếp nhận dữ liệu khi giá trị do LiveData 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, là một trong các Thành phần cấu trúc Android.
  • Bạn có thể sử dụng LiveData để cho phép giao diện người dùng của mình tự động cập nhật khi dữ liệu cập nhật.
  • Có thể quan sát LiveData, nghĩa là một đối tượng tiếp nhận dữ liệu như hoạt động hoặc một mảnh có thể được thông báo khi dữ liệu do đối tượng LiveData lưu giữ thay đổi.
  • LiveData chứa dữ liệu; đó là một trình bao bọc có thể 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 đang ở trạng thái vòng đời đang hoạt động 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à đối tượng LiveData có thể thay đổi giá trị. MutableLiveData là lớp chung, do đó, bạn cần chỉ định loại dữ liệu mà lớp đó lưu giữ.

  • Để thay đổi giá trị của dữ liệu do LiveData lưu giữ, hãy sử dụng phương thức setValue() trên biến LiveData.

Để đóng gói LiveData

  • Bạn phải chỉnh sửa được LiveData bên trong ViewModel. Bên ngoài ViewModel, LiveData phải dễ đọc. Bạn có thể triển khai lệnh này bằng thuộc tính dự phòng của Kotlin.
  • Thuộc tính sao lưu Kotlin cho phép bạn trả về nội dung từ phương thức getter khác với đối tượng chính xác.
  • Để đóng gói LiveData, hãy sử 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ột mẫu đối tượng tiếp nhận dữ liệu. "obobable" là đối tượng LiveData và đối tượng tiếp nhận dữ liệu là các phương thức trong bộ điều khiển giao diện người dùng, như các mảnh. Bất cứ khi nào dữ liệu được gói bên trong LiveData thay đổi, các phương thức quan sát trong bộ điều khiển giao diện người dùng sẽ đượ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 đố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().
  • 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 bộ điều khiển giao diện người dùng.

Khóa học từ Udacity:

Tài liệu dành cho nhà phát triển Android:

Các tài liệu khác:

Phần này liệt kê các bài tập về nhà có thể được giao cho học viên đang làm việc qua lớp học lập trình này trong khóa học do người hướng dẫn tổ chức. Người hướng dẫn có thể làm những việc sau:

  • Giao bài tập về nhà nếu được yêu cầu.
  • Trao đổi với học viên 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 những đề xuất này ít hay nhiều tùy ý. Do đó, họ có thể thoải mái giao 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ự mình làm việc qua lớp học lập trình này, hãy thoải mái sử dụng các bài tập về nhà này để kiểm tra kiến thức của bạn.

Trả lời những câu hỏi này

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 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 thuộc loại MutableLiveData.
  • Bên trong đối tượng ViewModel, hãy thay đổi loại 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 thuộc loại LiveData.
  • Bên trong bộ điều khiển giao diện người dùng, hãy thay đổi loại 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 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
  • Bị tạm dừng
  • Đã dừng

Câu hỏi 3

Trong mẫu đối tượng tiếp nhận dữ liệu LiveData, mục quan sát được 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 khóa học này, hãy xem trang đích của các lớp học lập trình cơ bản về Android Kotlin.