Kiến thức cơ bản về Kotlin trên Android 02.4: Kiến thức cơ bản về liên kết dữ liệu

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 các lớp học lập trình trước của khoá học này, bạn đã dùng hàm findViewById() để lấy các tham chiếu đến khung hiển thị. Khi ứng dụng của bạn có hệ phân cấp khung hiển thị phức tạp, findViewById() sẽ tốn nhiều tài nguyên và làm chậm ứng dụng, vì Android sẽ duyệt qua hệ phân cấp khung hiển thị, bắt đầu từ gốc cho đến khi tìm thấy khung hiển thị mong muốn. May mắn thay, có một cách hay hơn.

Để đặt dữ liệu trong các khung hiển thị, bạn đã sử dụng các tài nguyên chuỗi và đặt dữ liệu từ hoạt động. Sẽ hiệu quả hơn nếu khung hiển thị biết về dữ liệu. Và một lần nữa, thật may mắn là bạn có thể làm được điều này.

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng tính năng liên kết dữ liệu để loại bỏ nhu cầu sử dụng findViewById(). Bạn cũng tìm hiểu cách sử dụng tính năng liên kết dữ liệu để truy cập trực tiếp vào dữ liệu từ một khung hiển thị.

Kiến thức bạn cần có

Bạn cần thông thạo:

  • Hoạt động là gì và cách thiết lập một hoạt động có bố cục trong onCreate().
  • Tạo một thành phần hiển thị văn bản và đặt văn bản mà thành phần hiển thị văn bản đó hiển thị.
  • Sử dụng findViewById() để lấy thông tin tham chiếu đến một khung hiển thị.
  • Tạo và chỉnh sửa bố cục XML cơ bản cho một khung hiển thị.

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

  • Cách sử dụng Thư viện liên kết dữ liệu để loại bỏ các lệnh gọi không hiệu quả đến findViewById().
  • Cách truy cập trực tiếp vào dữ liệu ứng dụng từ XML.

Bạn sẽ thực hiện

  • Sửa đổi một ứng dụng để sử dụng tính năng liên kết dữ liệu thay vì findViewById() và truy cập trực tiếp vào dữ liệu từ các tệp XML bố cục.

Trong lớp học lập trình này, bạn sẽ bắt đầu bằng ứng dụng AboutMe và thay đổi ứng dụng để sử dụng tính năng liên kết dữ liệu. Ứng dụng sẽ có giao diện hoàn toàn giống nhau khi bạn hoàn tất!

Sau đây là những việc mà ứng dụng AboutMe có thể làm:

  • Khi người dùng mở ứng dụng, ứng dụng sẽ cho thấy một tên, một trường để nhập biệt hiệu, một nút Xong, một hình ảnh ngôi sao và văn bản có thể cuộn.
  • Người dùng có thể nhập biệt hiệu rồi nhấn vào nút Xong. Trường và nút có thể chỉnh sửa sẽ được thay thế bằng một khung hiển thị văn bản cho biết biệt hiệu đã nhập.


Bạn có thể sử dụng mã mà mình đã tạo trong lớp học lập trình trước hoặc tải mã AboutMeDataBinding-Starter xuống qua GitHub.

Đoạn mã bạn đã viết trong các lớp học lập trình trước đây sử dụng hàm findViewById() để lấy các tham chiếu đến khung hiển thị.

Mỗi khi bạn dùng findViewById() để tìm kiếm một khung hiển thị sau khi khung hiển thị đó được tạo hoặc tạo lại, hệ thống Android sẽ chuyển tải qua hệ phân cấp khung hiển thị trong thời gian chạy để tìm khung hiển thị đó. Đây không phải là vấn đề khi ứng dụng của bạn chỉ có một số ít khung hiển thị. Tuy nhiên, các ứng dụng phát hành công khai có thể có hàng chục khung hiển thị trong một bố cục và ngay cả khi có thiết kế tốt nhất, vẫn sẽ có các khung hiển thị lồng nhau.

Hãy nghĩ đến một bố cục tuyến tính chứa một khung hiển thị có thể cuộn chứa một khung hiển thị văn bản. Đối với hệ phân cấp khung hiển thị lớn hoặc sâu, việc tìm khung hiển thị có thể mất nhiều thời gian đến mức người dùng có thể nhận thấy ứng dụng chạy chậm hơn. Việc lưu vào bộ nhớ đệm các khung hiển thị trong các biến có thể giúp ích, nhưng bạn vẫn phải khởi tạo một biến cho mỗi khung hiển thị, trong mỗi không gian tên. Với nhiều khung hiển thị và nhiều hoạt động, điều này cũng sẽ cộng dồn.

Một giải pháp là tạo một đối tượng chứa thông tin tham chiếu đến từng khung hiển thị. Toàn bộ ứng dụng của bạn có thể dùng đối tượng này (gọi là đối tượng Binding). Kỹ thuật này được gọi là liên kết dữ liệu. Sau khi tạo một đối tượng liên kết cho ứng dụng, bạn có thể truy cập vào các khung hiển thị và dữ liệu khác thông qua đối tượng liên kết mà không cần phải đi qua hệ phân cấp khung hiển thị hoặc tìm kiếm dữ liệu.

Liên kết dữ liệu có những lợi ích sau:

  • Mã ngắn hơn, dễ đọc hơn và dễ duy trì hơn so với mã sử dụng findByView().
  • Dữ liệu và khung hiển thị được tách biệt rõ ràng. Lợi ích này của tính năng liên kết dữ liệu sẽ ngày càng trở nên quan trọng trong khoá học này.
  • Hệ thống Android chỉ duyệt qua hệ thống phân cấp khung hiển thị một lần để lấy từng khung hiển thị và quá trình này diễn ra trong quá trình khởi động ứng dụng, chứ không phải trong thời gian chạy khi người dùng đang tương tác với ứng dụng.
  • Bạn sẽ nhận được độ an toàn của kiểu để truy cập vào các khung hiển thị. (An toàn kiểu nghĩa là trình biên dịch xác thực các kiểu trong quá trình biên dịch và sẽ gửi lỗi nếu bạn cố gắng chỉ định sai kiểu cho một biến.)

Trong nhiệm vụ này, bạn sẽ thiết lập tính năng liên kết dữ liệu và sử dụng tính năng này để thay thế các lệnh gọi đến findViewById() bằng các lệnh gọi đến đối tượng liên kết.

Bước 1: Bật tính năng liên kết dữ liệu

Để sử dụng tính năng liên kết dữ liệu, bạn cần bật tính năng này trong tệp Gradle vì tính năng này không được bật theo mặc định. Điều này là do liên kết dữ liệu làm tăng thời gian biên dịch và có thể ảnh hưởng đến thời gian khởi động ứng dụng.

  1. Nếu bạn không có ứng dụng AboutMe từ một lớp học lập trình trước đây, hãy lấy mã AboutMeDataBinding-Starter trên GitHub. Mở tệp đó trong Android Studio.
  2. Mở tệp build.gradle (Module: app).
  3. Bên trong phần android, trước dấu ngoặc đóng, hãy thêm phần dataBinding rồi đặt enabled thành true.
dataBinding {
    enabled = true
}
  1. Khi được nhắc, hãy Đồng bộ hoá dự án. Nếu bạn không thấy lời nhắc, hãy chọn File > Sync Project with Gradle Files (Tệp > Đồng bộ hoá dự án với các tệp Gradle).
  2. Bạn có thể chạy ứng dụng nhưng sẽ không thấy thay đổi nào.

Bước 2: Thay đổi tệp bố cục để có thể sử dụng với tính năng liên kết dữ liệu

Để sử dụng tính năng liên kết dữ liệu, bạn cần gói bố cục XML bằng thẻ <layout>. Điều này là để lớp gốc không còn là một nhóm khung hiển thị nữa mà thay vào đó là một bố cục chứa các nhóm khung hiển thị và khung hiển thị. Sau đó, đối tượng liên kết có thể biết về bố cục và các khung hiển thị trong đó.

  1. Mở tệp activity_main.xml.
  2. Chuyển sang thẻ Văn bản.
  3. Thêm <layout></layout> làm thẻ ngoài cùng xung quanh <LinearLayout>.
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. Chọn Code > Reformat code (Mã > Định dạng lại mã) để sửa thụt lề mã.

    Các khai báo không gian tên cho một bố cục phải nằm trong thẻ ngoài cùng.
  1. Cắt phần khai báo không gian tên khỏi <LinearLayout> rồi dán vào thẻ <layout>. Thẻ mở <layout> của bạn sẽ có dạng như minh hoạ dưới đây và thẻ <LinearLayout> chỉ chứa các thuộc tính của khung hiển thị.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. Tạo và chạy ứng dụng để xác minh rằng bạn đã thực hiện đúng cách.

Bước 3: Tạo một đối tượng liên kết trong hoạt động chính

Thêm một tham chiếu đến đối tượng liên kết vào hoạt động chính để bạn có thể dùng đối tượng này truy cập vào các thành phần hiển thị:

  1. Mở tệp MainActivity.kt.
  2. Trước onCreate(), ở cấp cao nhất, hãy tạo một biến cho đối tượng liên kết. Biến này thường được gọi là binding.

    Loại binding, lớp ActivityMainBinding, được trình biên dịch tạo riêng cho hoạt động chính này. Tên này được lấy từ tên của tệp bố cục, tức là activity_main + Binding.
private lateinit var binding: ActivityMainBinding
  1. Nếu Android Studio nhắc, hãy nhập ActivityMainBinding. Nếu bạn không thấy lời nhắc, hãy nhấp vào ActivityMainBinding rồi nhấn Alt+Enter (Option+Enter trên máy Mac) để nhập lớp bị thiếu này. (Để biết thêm các phím tắt, hãy xem bài viết Phím tắt.)

    Câu lệnh import sẽ có dạng như câu lệnh dưới đây.
import com.example.android.aboutme.databinding.ActivityMainBinding

Tiếp theo, bạn sẽ thay thế hàm setContentView() hiện tại bằng một chỉ dẫn thực hiện những việc sau:

  • Tạo đối tượng liên kết.
  • Sử dụng hàm setContentView() trong lớp DataBindingUtil để liên kết bố cục activity_main với MainActivity. Hàm setContentView() này cũng xử lý một số chế độ thiết lập liên kết dữ liệu cho các khung hiển thị.
  1. Trong onCreate(), hãy thay thế lệnh gọi setContentView() bằng dòng mã sau.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. Nhập DataBindingUtil.
import androidx.databinding.DataBindingUtil

Bước 4: Sử dụng đối tượng liên kết để thay thế tất cả các lệnh gọi đến findViewById()

Giờ đây, bạn có thể thay thế tất cả các lệnh gọi đến findViewById() bằng các tham chiếu đến những khung hiển thị có trong đối tượng liên kết. Khi đối tượng liên kết được tạo, trình biên dịch sẽ tạo tên của các thành phần hiển thị trong đối tượng liên kết từ mã nhận dạng của các thành phần hiển thị trong bố cục, chuyển đổi các tên đó thành quy ước viết hoa kiểu lạc đà. Ví dụ: done_buttondoneButton trong đối tượng liên kết, nickname_edit trở thành nicknameEditnickname_text trở thành nicknameText.

  1. Trong onCreate(), hãy thay thế đoạn mã dùng findViewById() để tìm done_button bằng đoạn mã tham chiếu đến nút trong đối tượng liên kết.

    Thay thế mã này: findViewById<Button>(R.id.done_button)
    bằng: binding.doneButton

    Mã hoàn tất của bạn để đặt trình nghe lượt nhấp trong onCreate() sẽ có dạng như sau.
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. Làm tương tự cho tất cả các lệnh gọi đến findViewById() trong hàm addNickname().
    Thay thế tất cả các lần xuất hiện của findViewById<View>(R.id.id_view) bằng binding.idView. Thực hiện theo cách sau:
  • Xoá các định nghĩa cho biến editTextnicknameTextView cùng với các lệnh gọi đến findViewById(). Việc này sẽ gây ra lỗi.
  • Sửa lỗi bằng cách lấy các khung hiển thị nicknameText, nicknameEditdoneButton từ đối tượng binding thay vì các biến (đã xoá).
  • Thay thế view.visibility với binding.doneButton.visibility. Việc sử dụng binding.doneButton thay vì view được truyền vào giúp mã nhất quán hơn.

    Kết quả là đoạn mã sau:
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • Chức năng này không thay đổi. Bạn có thể xoá tham số view và cập nhật tất cả các lần sử dụng view để sử dụng binding.doneButton bên trong hàm này.
  1. nicknameText yêu cầu phải có một StringnicknameEdit.text là một Editable. Khi sử dụng tính năng liên kết dữ liệu, bạn cần chuyển đổi Editable thành String một cách rõ ràng.
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. Bạn có thể xoá các đối tượng nhập bị làm mờ.
  2. Kotlin hoá hàm bằng cách sử dụng apply{}.
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. Tạo và chạy ứng dụng của bạn...ứng dụng sẽ có giao diện và hoạt động giống hệt như trước.

Bạn có thể tận dụng tính năng liên kết dữ liệu để cung cấp trực tiếp một lớp dữ liệu cho một khung hiển thị. Kỹ thuật này giúp đơn giản hoá mã và cực kỳ hữu ích khi xử lý các trường hợp phức tạp hơn.

Trong ví dụ này, thay vì đặt tên và biệt hiệu bằng cách sử dụng tài nguyên chuỗi, bạn sẽ tạo một lớp dữ liệu cho tên và biệt hiệu. Bạn cung cấp lớp dữ liệu cho khung hiển thị bằng cách sử dụng tính năng liên kết dữ liệu.

Bước 1: Tạo lớp dữ liệu MyName

  1. Trong Android Studio, trong thư mục java, hãy mở tệp MyName.kt. Nếu bạn không có tệp này, hãy tạo một tệp Kotlin mới và gọi tệp đó là MyName.kt.
  2. Xác định một lớp dữ liệu cho tên và biệt hiệu. Sử dụng chuỗi trống làm giá trị mặc định.
data class MyName(var name: String = "", var nickname: String = "")

Bước 2: Thêm dữ liệu vào bố cục

Trong tệp activity_main.xml, tên hiện được đặt trong TextView từ một tài nguyên chuỗi. Bạn cần thay thế thông tin tham chiếu đến tên bằng thông tin tham chiếu đến dữ liệu trong lớp dữ liệu.

  1. Mở activity_main.xml trong thẻ Văn bản.
  2. Ở đầu bố cục, giữa thẻ <layout><LinearLayout>, hãy chèn thẻ <data></data>. Đây là nơi bạn sẽ kết nối khung hiển thị với dữ liệu.
<data>
  
</data>

Bên trong thẻ dữ liệu, bạn có thể khai báo các biến được đặt tên để giữ một tham chiếu đến một lớp.

  1. Trong thẻ <data>, hãy thêm một thẻ <variable>.
  2. Thêm một tham số name để đặt tên cho biến là "myName". Thêm một tham số type và đặt loại thành tên đủ điều kiện của lớp dữ liệu MyName (tên gói + tên biến).
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

Giờ đây, thay vì sử dụng tài nguyên chuỗi cho tên, bạn có thể tham chiếu đến biến myName.

  1. Thay thế android:text="@string/name" bằng đoạn mã dưới đây.

@={} là một chỉ thị để lấy dữ liệu được tham chiếu bên trong dấu ngoặc nhọn.

myName tham chiếu đến biến myName mà bạn đã xác định trước đó, trỏ đến lớp dữ liệu myName và tìm nạp thuộc tính name từ lớp.

android:text="@={myName.name}"

Bước 3: Tạo dữ liệu

Giờ đây, bạn có thể tham chiếu đến dữ liệu trong tệp bố cục. Tiếp theo, bạn sẽ tạo dữ liệu thực tế.

  1. Mở tệp MainActivity.kt.
  2. Phía trên onCreate(), hãy tạo một biến riêng tư, theo quy ước cũng được gọi là myName. Chỉ định cho biến một phiên bản của lớp dữ liệu MyName, truyền tên vào.
private val myName: MyName = MyName("Aleks Haecky")
  1. Trong onCreate(), hãy đặt giá trị của biến myName trong tệp bố cục thành giá trị của biến myName mà bạn vừa khai báo. Bạn không thể truy cập trực tiếp vào biến trong XML. Bạn cần truy cập vào đối tượng này thông qua đối tượng liên kết.
binding.myName = myName
  1. Điều này có thể cho thấy lỗi vì bạn cần làm mới đối tượng liên kết sau khi thực hiện các thay đổi. Tạo ứng dụng của bạn và lỗi này sẽ biến mất.

Bước 4: Sử dụng lớp dữ liệu cho biệt hiệu trong TextView

Bước cuối cùng là sử dụng lớp dữ liệu cho biệt hiệu trong TextView.

  1. Mở activity_main.xml.
  2. Trong chế độ xem văn bản nickname_text, hãy thêm một thuộc tính text. Tham chiếu nickname trong lớp dữ liệu, như minh hoạ bên dưới.
android:text="@={myName.nickname}"
  1. Trong ActivityMain, hãy thay thế
    nicknameText.text = nicknameEdit.text.toString()
    bằng mã để đặt biệt hiệu trong biến myName.
myName?.nickname = nicknameEdit.text.toString()

Sau khi biệt hiệu được đặt, bạn muốn mã của mình làm mới giao diện người dùng bằng dữ liệu mới. Để làm việc này, bạn phải vô hiệu hoá tất cả các biểu thức liên kết để chúng được tạo lại bằng dữ liệu chính xác.

  1. Thêm invalidateAll() sau khi đặt biệt hiệu để giao diện người dùng được làm mới bằng giá trị trong đối tượng liên kết đã cập nhật.
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. Tạo và chạy ứng dụng của bạn, ứng dụng sẽ hoạt động giống hệt như trước.

Dự án Android Studio: AboutMeDataBinding

Các bước sử dụng liên kết dữ liệu để thay thế các lệnh gọi đến findViewById():

  1. Bật tính năng liên kết dữ liệu trong phần android của tệp build.gradle:
    dataBinding { enabled = true }
  2. Dùng <layout> làm khung hiển thị gốc trong bố cục XML.
  3. Xác định một biến liên kết:
    private lateinit var binding: ActivityMainBinding
  4. Tạo một đối tượng liên kết trong MainActivity, thay thế setContentView:
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. Thay thế các lệnh gọi đến findViewById() bằng các tham chiếu đến khung hiển thị trong đối tượng liên kết. Ví dụ:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
    (Trong ví dụ này, tên của thành phần hiển thị được tạo theo quy ước viết hoa kiểu lạc đà từ id của thành phần hiển thị trong XML.)

Các bước để liên kết thành phần hiển thị với dữ liệu:

  1. Tạo một lớp dữ liệu cho dữ liệu của bạn.
  2. Thêm một khối <data> vào bên trong thẻ <layout>.
  3. Xác định một <variable> có tên và một loại là lớp dữ liệu.
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. Trong MainActivity, hãy tạo một biến có một thực thể của lớp dữ liệu. Ví dụ:
    private val myName: MyName = MyName("Aleks Haecky")
  1. Trong đối tượng liên kết, hãy đặt biến thành biến mà bạn vừa tạo:
    binding.myName = myName
  1. Trong XML, hãy đặt nội dung của khung hiển thị thành biến mà bạn đã xác định trong khối <data>. Sử dụng ký hiệu dấu chấm để truy cập vào dữ liệu bên trong lớp dữ liệu.
    android:text="@={myName.name}"

Khoá học của Udacity:

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

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

Tại sao bạn muốn giảm thiểu các lệnh gọi tường minh và ngầm định đến findViewById()?

  • Mỗi khi được gọi, findViewById() chuyển tải qua hệ phân cấp chế độ xem.
  • findViewById() chạy trên luồng chính hoặc luồng giao diện người dùng.
  • Các lệnh gọi này có thể làm chậm giao diện người dùng.
  • Ứng dụng của bạn ít có khả năng gặp sự cố hơn.

Câu hỏi 2

Bạn sẽ mô tả tính năng liên kết dữ liệu như thế nào?

Ví dụ: sau đây là một số điều bạn có thể nói về tính năng liên kết dữ liệu:

  • Ý tưởng chính về tính năng liên kết dữ liệu là tạo một đối tượng kết nối/ánh xạ/liên kết hai phần thông tin riêng biệt với nhau tại thời điểm biên dịch, để bạn không phải tìm kiếm dữ liệu tại thời gian chạy.
  • Đối tượng hiển thị các liên kết này cho bạn được gọi là đối tượng liên kết.
  • Đối tượng liên kết do trình biên dịch tạo.

Câu hỏi 3

Đâu KHÔNG phải là lợi ích của tính năng liên kết dữ liệu?

  • Mã ngắn hơn, dễ đọc và dễ duy trì hơn.
  • Dữ liệu và khung hiển thị được tách biệt rõ ràng.
  • Hệ thống Android chỉ truyền tải hệ phân cấp thành phần hiển thị một lần để lấy từng thành phần hiển thị.
  • Việc gọi findViewById() sẽ tạo ra lỗi trình biên dịch.
  • An toàn về kiểu để truy cập vào các khung hiển thị.

Câu hỏi 4

Thẻ <layout> có chức năng gì?

  • Bạn sẽ bao bọc thành phần này xung quanh khung hiển thị gốc trong bố cục.
  • Các liên kết được tạo cho tất cả các khung hiển thị trong một bố cục.
  • Thẻ này chỉ định khung hiển thị cấp cao nhất trong một bố cục XML sử dụng tính năng liên kết dữ liệu.
  • Bạn có thể dùng thẻ <data> bên trong <layout> để liên kết một biến với một lớp dữ liệu.

Câu hỏi 5

Đâu là cách chính xác để tham chiếu dữ liệu liên kết trong bố cục XML?

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

Bắt đầu bài học tiếp theo: 3.1: Tạo một mảnh

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