Sử dụng LiveData với ViewModel

1. Trước khi bắt đầu

Tại các lớp học lập trình trước, bạn đã tìm hiểu cách sử dụng ViewModel để lưu trữ dữ liệu ứng dụng. ViewModel cho phép giữ lại dữ liệu của ứng dụng khi có thay đổi về cấu hình. 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 ViewModel.

Lớp LiveData cũng là một phần của Thành phần kiến trúc Android và là lớp lưu giữ dữ liệu có thể quan sát được.

Điều kiện tiên quyết

  • Cách tải mã nguồn xuống từ GitHub và mở mã đó trong Android Studio.
  • Cách sử dụng hoạt động và mảnh để tạo và chạy một ứng dụng Android cơ bản trong Kotlin.
  • Cách vòng đời hoạt động và mảnh làm việc.
  • Cách sử dụng ViewModel để lưu giữ dữ liệu trên giao diện người dùng trong quá trình thay đổi cấu hình thiết bị.
  • Cách viết biểu thức lambda.

Kiến thức bạn sẽ học được

  • Cách sử dụng LiveDataMutableLiveData trong ứng dụng.
  • Cách đóng gói dữ liệu được lưu trữ trong ViewModel bằng LiveData.
  • 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 viết biểu thức liên kết trong tệp bố cục.

Sản phẩm bạn sẽ tạo ra

  • Sử dụng LiveData cho dữ liệu của ứng dụng (từ, số từ và điểm số) trong ứng dụng Unscramble (Xếp từ).
  • Thêm các phương thức tiếp nhận dữ liệu mà sẽ được thông báo khi dữ liệu thay đổi, tự động cập nhật chế độ xem văn bản từ ngữ bị xáo trộn.
  • Viết các biểu thức liên kết trong tệp bố cục. Các biểu thức này được kích hoạt khi LiveData cơ sở thay đổi. Điểm số, số từ và chế độ xem văn bản từ bị xáo trộn được cập nhật tự động.

Bạn cần có

  • Một máy tính đã cài đặt Android Studio.
  • Mã giải pháp từ lớp học lập trình trước (Ứng dụng Unscramble với ViewModel).

Tải mã khởi đầu xuống cho lớp học lập trình này

Lớp học lập trình này dùng ứng dụng Unscramble đã được bạn tạo trong lớp học lập trình trước (Lưu trữ dữ liệu trong ViewModel) làm mã khởi đầu.

2. Tổng quan về ứng dụng khởi đầu

Lớp học lập trình này sử dụng mã giải pháp Unscramble (Xếp từ) mà bạn đã làm quen trong lớp học lập trình trước đó. Ứng dụng hiển thị một từ bị xáo trộn để người chơi xếp từ đó. Người chơi có thể thử vô số lần để đoán ra chính xác từ đó. Dữ liệu của ứng dụng như từ hiện tại, điểm số của người chơi và số từ được lưu trong ViewModel. Tuy nhiên, giao diện người dùng của ứng dụng không phản ánh giá trị mới về điểm và số từ. Trong lớp học lập trình này, bạn sẽ triển khai các tính năng còn thiếu bằng LiveData.

a20e6e45e0d5dc6f.png

3. Livedata là gì

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 số đặc điểm của LiveData:

  • 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ỳ loại dữ liệu nào.
  • 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 nhận biết được vòng đời. Khi bạn đính kèm một đối tượng tiếp nhận dữ liệu vào LiveData, đối tượng tiếp nhận dữ liệu này sẽ được liên kết với một LifecycleOwner (thường là một hoạt động hoặc mảnh). LiveData 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. Bạn có thể đọc thêm về LiveData và quan sát tại đây.

Cập nhật giao diện người dùng trong mã khởi đầu

Trong mã khởi đầu, phương thức updateNextWordOnScreen() được gọi một cách rõ ràng mỗi khi bạn muốn hiện một từ bị xáo trộn mới trong giao diện người dùng. Bạn gọi phương thức này trong quá trình khởi chạy trò chơi và khi người chơi nhấp vào nút Submit (Gửi) hoặc Skip (Bỏ qua). Phương thức này được gọi từ các phương thức onViewCreated(), restartGame(), onSkipWord()onSubmitWord(). Với Livedata, bạn không còn phải gọi phương thức này từ nhiều nơi để cập nhật giao diện người dùng. Bạn chỉ thực hiện việc này một lần trong đối tượng tiếp nhận dữ liệu.

4. Thêm LiveData vào từ bị xáo trộn hiện tại

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

MutableLiveData

MutableLiveData là phiên bản có thể biến đổi của LiveData, tức là giá trị của dữ liệu được lưu trữ trong đó có thể thay đổi.

  1. Trong GameViewModel, thay đổi loại biến _currentScrambledWord thành MutableLiveData<String>. LiveDataMutableLiveData là các lớp chung, do vậy, bạn cần chỉ định loại dữ liệu mà các lớp đó giữ.
  2. Thay đổi loại biến _currentScrambledWord thành val vì giá trị của đối tượng LiveData/MutableLiveData sẽ giữ nguyên và chỉ có dữ liệu được lưu trữ trong đối tượng mới thay đổi.
private val _currentScrambledWord = MutableLiveData<String>()
  1. Thay đổi trường sao lưu, thay đổi loại currentScrambledWord thành LiveData<String>, vì trường này không thể thay đổi. Android Studio sẽ hiển thị một số lỗi mà bạn sẽ khắc phục trong các bước tiếp theo.
val currentScrambledWord: LiveData<String>
   get() = _currentScrambledWord
  1. Để truy cập vào dữ liệu trong đối tượng LiveData, sử dụng thuộc tính value. Trong GameViewModel bên trong phương thức getNextWord(), trong khối else, thay đổi tham chiếu của _currentScrambledWord thành _currentScrambledWord.value.
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

5. Đính kèm đối tượng tiếp nhận dữ liệu vào đối tượng LiveData

Ở nhiệm vụ này, bạn thiết lập một đối tượng tiếp nhận dữ liệu trong thành phần ứng dụng, GameFragment. Đối tượng tiếp nhận dữ liệu, được bạn thêm vào, sẽ quan sát các thay đổi đối với dữ liệu của ứng dụng currentScrambledWord. 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. Cho nên, đối tượng tiếp nhận dữ liệu trong GameFragment sẽ chỉ được thông báo khi GameFragment ở trạng thái STARTED hoặc RESUMED.

  1. Trong GameFragment, xoá phương thức updateNextWordOnScreen() và tất cả các lệnh gọi đến phương thức đó. Bạn không cần đến phương pháp này, do bạn sẽ đính kèm đối tượng tiếp nhận dữ liệu vào LiveData.
  2. Trong onSubmitWord(), sửa đổi khối if-else trống như sau. Phương thức hoàn chỉnh sẽ có dạng như sau.
private fun onSubmitWord() {
    val playerWord = binding.textInputEditText.text.toString()

    if (viewModel.isUserWordCorrect(playerWord)) {
        setErrorTextField(false)
        if (!viewModel.nextWord()) {
            showFinalScoreDialog()
        }
    } else {
        setErrorTextField(true)
    }
}
  1. Đính kèm một đối tượng tiếp nhận dữ liệu cho currentScrambledWord LiveData. Trong GameFragment khi kết thúc lệnh gọi lại onViewCreated(), gọi phương thức observe() trên currentScrambledWord.
// Observe the currentScrambledWord LiveData.
viewModel.currentScrambledWord.observe()

Android Studio sẽ hiển thị lỗi về tham số còn thiếu. Bạn sẽ khắc phục lỗi trong bước tiếp theo.

  1. Chuyển viewLifecycleOwner dưới dạng tham số đầu tiên vào phương thức observe(). viewLifecycleOwner thể hiện vòng đời của Chế độ xem mảnh. Tham số này giúp LiveData nhận biết được vòng đời của GameFragment và chỉ thông báo cho đối tượng tiếp nhận dữ liệu khi GameFragment ở trong trạng thái hoạt động (STARTED hoặc RESUMED).
  2. Thêm một lambda làm tham số thứ hai với tham số hàm là newWord. newWord sẽ chứa giá trị của từ bị xáo trộn mới.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
   })

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. Biểu thức lambda luôn được bao quanh bởi dấu ngoặc nhọn { }.

  1. Trong nội dung hàm của biểu thức lambda, gán newWord cho chế độ xem văn bản từ bị xáo trộn.
// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Biên dịch và 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, chế độ xem văn bản từ bị xáo trộn sẽ được tự động cập nhật trong đối tượng tiếp nhận dữ liệu LiveData, chứ không phải trong phương thức updateNextWordOnScreen().

6. Đính kèm đối tượng tiếp nhận dữ liệu vào điểm số và số từ

Như nhiệm vụ trước, trong nhiệm vụ này, bạn sẽ thêm LiveData vào dữ liệu khác trong ứng dụng, điểm số và số từ để giao diện người dùng được cập nhật với các giá trị chính xác của điểm số và số từ trong trò chơi.

Bước 1: Tổng hợp điểm và số từ bằng LiveData

  1. Trong GameViewModel, thay đổi loại của _score và biến lớp _currentWordCount thành val.
  2. Thay đổi loại dữ liệu của các biến _score_currentWordCount thành MutableLiveData, rồi khởi chạy các biến đó cho 0.
  3. Thay đổi loại trường sao lưu thành LiveData<Int>
private val _score = MutableLiveData(0)
val score: LiveData<Int>
   get() = _score

private val _currentWordCount = MutableLiveData(0)
val currentWordCount: LiveData<Int>
   get() = _currentWordCount
  1. Trong GameViewModel ở đầu phương thức reinitializeData(), thay đổi tham chiếu của _score_currentWordCount thành _score.value_currentWordCount.value tương ứng.
fun reinitializeData() {
   _score.value = 0
   _currentWordCount.value = 0
   wordsList.clear()
   getNextWord()
}
  1. Trong GameViewModel, bên trong phương thức nextWord(), thay đổi tham chiếu của _currentWordCount thành _currentWordCount.value!!.
fun nextWord(): Boolean {
    return if (_currentWordCount.value!! < MAX_NO_OF_WORDS) {
           getNextWord()
           true
       } else false
   }
  1. Trong GameViewModel, bên trong phương thức increaseScore()getNextWord(), thay đổi tham chiếu của _score_currentWordCount thành _score.value_currentWordCount.value tương ứng. Android Studio sẽ hiển thị lỗi để bạn xem do _score không còn là số nguyên nữa mà là LiveData, bạn sẽ khắc phục lỗi này trong các bước tiếp theo.
  2. Sử dụng hàm Kotlin plus() để tăng giá trị _score. Giá trị này bổ sung giá trị rỗng an toàn.
private fun increaseScore() {
    _score.value = (_score.value)?.plus(SCORE_INCREASE)
}
  1. Tương tự, sử dụng hàm Kotlin inc() để giá trị tăng thêm một bằng giá trị rỗng an toàn.
private fun getNextWord() {
   ...
    } else {
        _currentScrambledWord.value = String(tempWord)
        _currentWordCount.value = (_currentWordCount.value)?.inc()
        wordsList.add(currentWord)
       }
   }
  1. Trong GameFragment, truy cập giá trị của score bằng thuộc tính value. Bên trong phương thức showFinalScoreDialog(), thay đổi viewModel.score thành viewModel.score.value.
private fun showFinalScoreDialog() {
   MaterialAlertDialogBuilder(requireContext())
       .setTitle(getString(R.string.congratulations))
       .setMessage(getString(R.string.you_scored, viewModel.score.value))
       ...
       .show()
}

Bước 2: Đính kèm đối tượng tiếp nhận dữ liệu vào điểm số và số từ

Trong ứng dụng, điểm số và số từ không được cập nhật. Bạn sẽ cập nhật điểm số và số từ bằng LiveData đối tượng tiếp nhận dữ liệu.

  1. Trong GameFragment bên trong phương thức onViewCreated(), xoá mã cập nhật điểm số và chế độ xem văn bản từ.

Xoá:

binding.score.text = getString(R.string.score, 0)
binding.wordCount.text = getString(R.string.word_count, 0, MAX_NO_OF_WORDS)
  1. Trong GameFragment ở cuối phương thức onViewCreated(), đính kèm đối tượng tiếp nhận dữ liệu cho score. Chuyển viewLifecycleOwner làm tham số đầu tiên vào đối tượng tiếp nhận dữ liệu và biểu thức lambda làm tham số thứ hai. Bên trong biểu thức lambda, chuyển điểm mới làm tham số và bên trong nội dung hàm, đặt điểm mới thành chế độ xem văn bản.
viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })
  1. Ở cuối phương thức onViewCreated(), đính kèm đối tượng tiếp nhận dữ liệu cho currentWordCount LiveData. Chuyển viewLifecycleOwner làm tham số đầu tiên vào đối tượng tiếp nhận dữ liệu và biểu thức lambda làm tham số thứ hai. Bên trong biểu thức lambda, hãy chuyển số từ mới làm tham số và trong nội dung hàm, đặt số từ mới cùng với MAX_NO_OF_WORDS thành chế độ xem văn bản.
viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })

Các đối tượng tiếp nhận dữ liệu mới sẽ được kích hoạt khi giá trị của điểm số và số từ thay đổi bên trong ViewModel, trong suốt thời gian hoạt động của chủ sở hữu vòng đời, tức là GameFragment.

  1. Chạy ứng dụng của bạn để xem điều kỳ diệu xảy ra. Chơi trò chơi với một vài từ. Điểm số và số từ cũng được cập nhật chính xác trên màn hình. Lưu ý rằng bạn đang không cập nhật các chế độ xem văn bản này dựa trên một số điều kiện trong mã. scorecurrentWordCountLiveData và các đối tượng tiếp nhận dữ liệu tương ứng tự động được gọi khi giá trị cơ bản thay đổi.

80e118245bdde6df.png

7. Sử dụng LiveData với liên kết dữ liệu

Trong các nhiệm vụ trước, ứng dụng của bạn đã theo dõi những thay đổi về dữ liệu trong mã. Tương tự, ứng dụng có thể theo dõi các thay đổi về dữ liệu từ bố cục. Với tính năng Liên kết dữ liệu, khi giá trị LiveData có thể quan sát thay đổi, các thành phần giao diện người dùng trong bố cục, mà liên kết với giá trị này, cũng sẽ được thông báo và giao diện người dùng có thể được cập nhật từ bên trong bố cục.

Khái niệm: Liên kết dữ liệu

Trong các lớp học lập trình trước đây, bạn đã thấy tuỳ chọn Liên kết chế độ xem, đây là liên kết một chiều. Bạn có thể liên kết các chế độ xem với mã nhưng không theo chiều ngược lại.

Trình làm mới để liên kết chế độ xem:

Liên kết chế độ xem là một tính năng cho phép bạn truy cập chế độ xem trong mã dễ dàng hơn. Tính năng này tạo một lớp liên kết cho mỗi tệp bố cục XML. Một thực thể của lớp liên kết có chứa thông tin tham chiếu trực tiếp đến tất cả các chế độ xem có mã (ID) trong bố cục tương ứng. Ví dụ: ứng dụng Unscramble hiện đang sử dụng liên kết chế độ xem, để chế độ xem có thể được tham chiếu trong mã bằng cách sử dụng lớp liên kết được tạo.

Ví dụ:

binding.textViewUnscrambledWord.text = newWord
binding.score.text = getString(R.string.score, newScore)
binding.wordCount.text =
                  getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)

Khi sử dụng tính năng liên kết chế độ xem, bạn không thể tham chiếu dữ liệu ứng dụng trong chế độ xem (tệp bố cục). Bạn có thể hoàn thành việc này khi sử dụng tính năng Liên kết dữ liệu.

Liên kết dữ liệu

Thư viện liên kết dữ liệu cũng là một phần trong thư viện Android Jetpack. Tính năng liên kết dữ liệu ràng buộc các thành phần giao diện người dùng trong bố cục với các nguồn dữ liệu trong ứng dụng của bạn bằng cách sử dụng định dạng khai báo. Bạn sẽ tìm hiểu định dạng đó sau trong lớp học lập trình này.

Nói đơn giản thì Liên kết dữ liệu là quá trình liên kết dữ liệu (từ mã) với khung hiển thị (view) + liên kết khung hiển thị (liên kết khung hiển thị với mã):

Ví dụ về cách sử dụng tính năng liên kết khung hiển thị trong bộ điều khiển trên giao diện người dùng

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

Ví dụ về cách sử dụng tính năng liên kết dữ liệu trong tệp bố cục

android:text="@{gameViewModel.currentScrambledWord}"

Ví dụ trên cho thấy cách sử dụng Thư viện liên kết dữ liệu để trực tiếp gán dữ liệu ứng dụng vào các chế độ xem/tiện ích trong tệp bố cục. Lưu ý rằng bạn có thể sử dụng cú pháp @{} trong biểu thức gán.

Lợi ích chính của việc sử dụng tính năng liên kết dữ liệu là bạn có thể xoá nhiều lệnh gọi khung giao diện người dùng trong hoạt động, giúp duy trì các lần gọi này đơn giản và dễ dàng hơn. Việc này cũng có thể cải thiện hiệu suất của ứng dụng và giúp ngăn ngừa rò rỉ bộ nhớ lẫn ngoại lệ con trỏ rỗng.

Bước 1: Thay đổi liên kết chế độ xem thành liên kết dữ liệu

  1. Trong tệp build.gradle(Module), kích hoạt thuộc tính dataBinding trong mục buildFeatures.

Thay thế

buildFeatures {
   viewBinding = true
}

bằng

buildFeatures {
   dataBinding = true
}

Đồng bộ hoá gradle khi được Android Studio nhắc.

  1. Để sử dụng tính năng liên kết dữ liệu trong bất kỳ dự án Kotlin nào, bạn nên áp dụng trình bổ trợ kotlin-kapt. Bước này đã được thực hiện trong tệp build.gradle(Module).
plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-kapt'
}

Các bước ở trên sẽ tự động tạo một lớp liên kết cho mọi tệp XML bố cục trong ứng dụng. Nếu tên tệp bố cục là activity_main.xml, thì lớp tự động tạo của bạn sẽ được gọi là ActivityMainBinding.

Bước 2: Chuyển đổi tệp bố cục thành bố cục liên kết dữ liệu

Các tệp bố cục liên kết dữ liệu hơi khác nhau và bắt đầu bằng thẻ gốc <layout>, kèm với một thành phần tuỳ chọn <data> và thành phần gốc view. Phần tử chế độ xem này là phần tử gốc của bạn trong tệp bố cục không ràng buộc.

  1. Mở game_fragment.xml, chọn thẻ .
  2. Để chuyển đổi bố cục thành bố cục Data Binding (Liên kết dữ liệu), hãy gói thành phần gốc trong thẻ <layout>. Bạn cũng sẽ phải di chuyển các định nghĩa của không gian tên (các thuộc tính bắt đầu bằng xmlns:) sang phần tử gốc mới. Thêm thẻ <data></data> bên trong thẻ <layout> ở trên thành phần gốc. Android Studio tạo điều kiện thuận lợi để thực hiện việc này một cách tự động: Nhấp chuột phải vào thành phần gốc (ScrollView), chọn Show Context Actions > Convert to data binding layout (Hiển thị hành động theo ngữ cảnh > Chuyển đổi sang bố cục liên kết dữ liệu)

8d48f58c2bdccb52.png

  1. Bố cục của bạn sẽ có dạng như sau:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools">

   <data>

   </data>

   <ScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <androidx.constraintlayout.widget.ConstraintLayout
         ...
       </androidx.constraintlayout.widget.ConstraintLayout>
   </ScrollView>
</layout>
  1. Trong GameFragment, ở đầu phương thức onCreateView(), thay đổi cách tạo bản sao của biến binding để sử dụng tính năng liên kết dữ liệu.

Thay thế

binding = GameFragmentBinding.inflate(inflater, container, false)

bằng

binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)
  1. Biên dịch mã; bạn có thể biên dịch mà không gặp bất kỳ vấn đề nào. Ứng dụng của bạn hiện sử dụng liên kết dữ liệu và chế độ xem trong bố cục có thể truy cập dữ liệu ứng dụng.

8. Thêm các biến liên kết dữ liệu

Trong nhiệm vụ này, bạn sẽ thêm các thuộc tính trong tệp bố cục để truy cập vào dữ liệu ứng dụng từ viewModel. Bạn sẽ khởi chạy các biến bố cục trong mã.

  1. Trong game_fragment.xml, bên trong thẻ <data>, thêm một thẻ con có tên là <variable>, khai báo một thuộc tính có tên là gameViewModel và thuộc loại GameViewModel. Bạn sẽ dùng thẻ này để liên kết dữ liệu trong ViewModel với bố cục.
<data>
   <variable
       name="gameViewModel"
       type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>

Lưu ý rằng loại của gameViewModel chứa tên gói. Bảo đảm tên gói này khớp với tên gói trong ứng dụng của bạn.

  1. Bên dưới khai báo gameViewModel, thêm một biến khác vào bên trong thẻ <data> của loại Integer và đặt tên cho biến đó là maxNoOfWords. Bạn sẽ sử dụng biến này để liên kết với biến trong ViewModel để lưu trữ số từ trên mỗi lượt chơi.
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>
  1. Trong GameFragment ở đầu phương thức onViewCreated(), hãy khởi chạy các biến bố cục gameViewModelmaxNoOfWords.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.gameViewModel = viewModel

   binding.maxNoOfWords = MAX_NO_OF_WORDS
...
}
  1. LiveData có thể quan sát nhận biết được vòng đời nên bạn phải chuyển chủ sở hữu vòng đời vào bố cục. TrongGameFragment , bên trongonViewCreated() ở dưới phần khởi chạy của các biến liên kết, thêm vào mã sau.
   // Specify the fragment view as the lifecycle owner of the binding.
   // This is used so that the binding can observe LiveData updates
   binding.lifecycleOwner = viewLifecycleOwner

Nên nhớ rằng bạn đã triển khai một chức năng tương tự khi triển khai đối tượng tiếp nhận dữ liệu LiveData. Bạn đã chuyển viewLifecycleOwner dưới dạng một trong các tham số cho đối tượng tiếp nhận dữ liệu LiveData .

9. Sử dụng biểu thức liên kết

Biểu thức liên kết được viết bên trong bố cục trong các thuộc tính bố cục (chẳng hạn như android:text) khi tham chiếu đến các thuộc tính bố cục. Thuộc tính bố cục được khai báo ở đầu tệp bố cục liên kết dữ liệu, thông qua thẻ <variable>. Khi bất kỳ biến phụ thuộc nào thay đổi, "Thư viện DB" sẽ chạy biểu thức ràng buộc của bạn (và do đó cập nhật chế độ xem). Khi sử dụng Thư viện liên kết dữ liệu, bạn được sử dụng miễn phí tính năng phát hiện thay đổi. Đây là một tính năng tối ưu hoá tuyệt vời.

Cú pháp cho biểu thức liên kết

Biểu thức liên kết bắt đầu bằng biểu tượng @ và nằm giữa các dấu ngoặc nhọn {}. Trong ví dụ sau, văn bản TextView được đặt thành thuộc tính firstName của biến user:

Ví dụ:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

Bước 1: Thêm biểu thức liên kết vào từ hiện tại

Trong bước này, bạn liên kết chế độ xem văn bản hiện tại với đối tượng LiveData trongViewModel ,

  1. Trong game_fragment.xml, thêm thuộc tính text vào chế độ xem văn bản textView_unscrambled_word. Sử dụng biến bố cục mới là gameViewModel và gán @{gameViewModel.currentScrambledWord} cho thuộc tính text.
<TextView
   android:id="@+id/textView_unscrambled_word"
   ...
   android:text="@{gameViewModel.currentScrambledWord}"
   .../>
  1. Trong GameFragment, xoá mã đối tượng tiếp nhận dữ liệu LiveData cho currentScrambledWord: Bạn không còn cần đến mã đối tượng tiếp nhận dữ liệu trong mảnh nữa. Bố cục sẽ trực tiếp nhận được thông tin cập nhật về những thay đổi đối với LiveData.

Xoá:

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })
  1. Chạy ứng dụng của bạn, ứng dụng sẽ hoạt động như trước đây. Tuy nhiên, vào lúc này chế độ xem văn bản từ bị xáo trộn sử dụng biểu thức liên kết để cập nhật giao diện người dùng chứ không phải LiveData đối tượng tiếp nhận dữ liệu.

Bước 2: Thêm biểu thức liên kết vào điểm số và số từ

Tài nguyên trong biểu thức liên kết dữ liệu

Biểu thức liên kết dữ liệu có thể tham chiếu các tài nguyên ứng dụng với cú pháp sau.

Ví dụ:

android:padding="@{@dimen/largePadding}"

Trong ví dụ trên, thuộc tính padding được gán giá trị largePadding từ tệp tài nguyên dimen.xml.

Bạn cũng có thể chuyển thuộc tính bố cục dưới dạng tham số tài nguyên.

Ví dụ:

android:text="@{@string/example_resource(user.lastName)}"

strings.xml

<string name="example_resource">Last Name: %s</string>

Trong ví dụ trên, example_resource là một tài nguyên chuỗi với phần giữ chỗ %s. Bạn đang chuyển user.lastName dưới dạng một tham số tài nguyên trong biểu thức liên kết, trong đó user là biến bố cục.

Trong bước này, bạn sẽ thêm biểu thức liên kết vào chế độ xem điểm số và văn bản số từ, chuyển tham số tài nguyên. Bước này tương tự như bước bạn đã làm cho textView_unscrambled_word ở trên.

  1. Trong game_fragment.xml, cập nhật thuộc tính text cho chế độ xem văn bản word_count bằng biểu thức liên kết sau. Sử dụng tài nguyên chuỗi word_count và chuyển vào gameViewModel.currentWordCount còn maxNoOfWords làm tham số tài nguyên.
<TextView
   android:id="@+id/word_count"
   ...
   android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
   .../>
  1. Cập nhật thuộc tính text cho chế độ xem văn bản score bằng biểu thức liên kết sau. Sử dụng tài nguyên chuỗi score và chuyển vào gameViewModel.score làm tham số tài nguyên.
<TextView
   android:id="@+id/score"
   ...
   android:text="@{@string/score(gameViewModel.score)}"
   ... />
  1. Xoá đối tượng tiếp nhận dữ liệu LiveData khỏi GameFragment. Bạn không cần các đối tượng tiếp nhận dữ liệu đó nữa, biểu thức liên kết sẽ cập nhật giao diện người dùng khi LiveData tương ứng thay đổi.

Xoá:

viewModel.score.observe(viewLifecycleOwner,
   { newScore ->
       binding.score.text = getString(R.string.score, newScore)
   })

viewModel.currentWordCount.observe(viewLifecycleOwner,
   { newWordCount ->
       binding.wordCount.text =
           getString(R.string.word_count, newWordCount, MAX_NO_OF_WORDS)
   })
  1. Chạy ứng dụng và chơi thử một số từ. Hiện tại, mã của bạn sử dụng LiveData và biểu thức liên kết để cập nhật giao diện người dùng.

7880e60dc0a6f95c.png 9ef2fdf21ffa5c99.png

Xin chúc mừng! Bạn đã tìm hiểu cách sử dụng LiveData với LiveData đối tượng tiếp nhận dữ liệu và LiveData với biểu thức ràng buộc.

10. Kiểm thử ứng dụng Unscramble với Talkback được bật

Bạn học khoá này vì muốn xây dựng một ứng dụng tiếp cận càng nhiều người dùng càng tốt. Một số người dùng có thể sử dụng TalkBack để truy cập và thao tác trên ứng dụng của bạn. TalkBack là trình đọc màn hình của Google có sẵn trên thiết bị Android. TalkBack cung cấp tính năng phản hồi bằng giọng nói để người dùng có thể sử dụng thiết bị mà không cần nhìn màn hình.

Khi bật Talkback, bảo đảm rằng người chơi có thể chơi trò chơi.

  1. Bật Talkback trên thiết bị theo hướng dẫn này.
  2. Quay lại ứng dụng Unscramble.
  3. Dùng TalkBack để khám phá ứng dụng theo hướng dẫn này. Vuốt sang phải để di chuyển lần lượt qua từng thành phần màn hình rồi vuốt sang trái để di chuyển theo hướng ngược lại. Nhấn đúp vào vị trí bất kỳ để chọn. Kiểm tra để đảm bảo rằng bạn có thể truy cập vào tất cả thành phần của ứng dụng qua thao tác vuốt.
  4. Bảo đảm rằng người dùng Talkback có thể chuyển đến từng mục trên màn hình.
  5. Quan sát TalkBack cố gắng đọc từ bị xáo trộn như một từ. Điều này có thể khiến người chơi bối rối vì đây không phải là một từ thực.
  6. Một trải nghiệm người dùng tốt hơn là yêu cầu Talkback đọc to các ký tự riêng lẻ của từ bị xáo trộn. Trong GameViewModel, chuyển đổi từ bị xáo trộn String thành một chuỗi Spannable. Chuỗi spannable là một chuỗi có thêm một số thông tin đi kèm. Trong trường hợp này, chúng ta muốn liên kết chuỗi đó với TtsSpan của TYPE_VERBATIM để công cụ chuyển văn bản sang lời nói đọc to chính xác từ bị xáo trộn, từng ký tự một.
  7. Trong GameViewModel, hãy dùng mã sau đây để sửa đổi cách khai báo biến currentScrambledWord:
val currentScrambledWord: LiveData<Spannable> = Transformations.map(_currentScrambledWord) {
    if (it == null) {
        SpannableString("")
    } else {
        val scrambledWord = it.toString()
        val spannable: Spannable = SpannableString(scrambledWord)
        spannable.setSpan(
            TtsSpan.VerbatimBuilder(scrambledWord).build(),
            0,
            scrambledWord.length,
            Spannable.SPAN_INCLUSIVE_INCLUSIVE
        )
        spannable
    }
}

Biến này hiện là LiveData<Spannable> thay vì là LiveData<String>. Bạn đừng lo phải tìm hiểu toàn bộ thông tin chi tiết về cách hoạt động của tính năng này, chỉ cần biết việc triển khai sẽ sử dụng phép biến đổi LiveData để chuyển đổi từ bị xáo trộn hiện thời String thành một chuỗi spannable có thể được xử lý một cách thích hợp bằng dịch vụ hỗ trợ tiếp cận. Trong lớp học lập trình tiếp theo, bạn sẽ tìm hiểu thêm về các phép biến đổi LiveData. Các biến đổi này cho phép bạn trả về một thực thể LiveData khác dựa trên giá trị của LiveData tương ứng.

  1. Chạy ứng dụng Unscramble, khám phá ứng dụng của bạn bằng Talkback. Lúc này TalkBack sẽ đọc to các ký tự riêng lẻ của từ bị xáo trộn.

Để biết thêm thông tin về cách khiến ứng dụng của bạn dễ tiếp cận hơn, xem các nguyên tắc này.

11. Xoá mã không dùng đến

Tốt nhất là xoá mã đã chết, không dùng đến, không mong muốn đối với mã giải pháp. Điều này giúp dễ dàng duy trì mã, giúp các thành viên mới trong nhóm dễ dàng hiểu mã hơn.

  1. Trong GameFragment, xoá phương thức getNextScrambledWord()onDetach().
  2. Trong GameViewModel, xoá phương thức onCleared().
  3. Xoá mọi mục nhập (import) không dùng đến ở đầu tệp nguồn. Các mục này sẽ chuyển sang màu xám.

Bạn không cần đến các câu lệnh nhật ký nữa nên có thể xoá khỏi mã nếu muốn.

  1. [Không bắt buộc] Xoá câu lệnh Log trong các tệp nguồn(GameFragment.ktGameViewModel.kt) mà bạn đã thêm vào trong lớp học lập trình trước đó, để hiểu vòng đời của ViewModel.

12. Đoạn mã giải pháp

Mã giải pháp cho lớp học lập trình này nằm trong dự án dưới đây.

  1. Chuyển đến trang kho lưu trữ GitHub được cung cấp cho dự án.
  2. Xác minh rằng tên nhánh khớp với tên nhánh được chỉ định trong lớp học lập trình. Ví dụ: trong ảnh chụp màn hình sau đây, tên nhánh là main.

1e4c0d2c081a8fd2.png

  1. Trên trang GitHub cho dự án này, nhấp vào nút Code (Mã). Thao tác này sẽ khiến một cửa sổ bật lên.

1debcf330fd04c7b.png

  1. Trong cửa sổ bật lên, nhấp vào nút Download ZIP (Tải tệp ZIP xuống) để lưu dự án vào máy tính. Chờ quá trình tải xuống hoàn tất.
  2. Xác định vị trí của tệp trên máy tính (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
  3. Nhấp đúp vào tệp ZIP để giải nén. Thao tác này sẽ tạo một thư mục mới chứa các tệp dự án.

Mở dự án trong Android Studio

  1. Khởi động Android Studio.
  2. Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy nhấp vào Open (Mở).

d8e9dbdeafe9038a.png

Lưu ý: Nếu Android Studio đã mở thì chuyển sang chọn tuỳ chọn File (Tệp) > Open (Mở) trong trình đơn.

8d1fda7396afe8e5.png

  1. Trong trình duyệt tệp, hãy chuyển đến vị trí của thư mục dự án chưa giải nén (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
  2. Nhấp đúp vào thư mục dự án đó.
  3. Chờ Android Studio mở dự án.
  4. Nhấp vào nút Chạy 8de56cba7583251f.png để tạo bản dựng và chạy ứng dụng. Đảm bảo ứng dụng được xây dựng như mong đợi.

13. Tóm tắt

  • 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 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 nhận biết được vòng đời. Khi bạn đính kèm một đối tượng tiếp nhận dữ liệu vào LiveData, đối tượng tiếp nhận dữ liệu này sẽ được liên kết với một LifecycleOwner (thường là một Hoạt động hoặc Mảnh). LiveData 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 hoạt động như STARTED hoặc RESUMED. Bạn có thể đọc thêm về LiveData và quan sát tại đây.
  • Ứng dụng có thể theo dõi các thay đổi về LiveData từ bố cục bằng cách sử dụng các biểu thức Liên kết và liên kết dữ liệu.
  • Biểu thức liên kết được viết bên trong bố cục trong các thuộc tính bố cục (chẳng hạn như android:text) khi tham chiếu đến các thuộc tính bố cục.

14. Tìm hiểu thêm

Bài đăng trên blog