1. Trước khi bắt đầu
Bạn đã tìm hiểu cách sử dụng các hoạt động (activity), mảnh (fragment), ý định (intent), mối liên kết dữ liệu (data binding) và thành phần điều hướng (navigation) cũng như nắm được kiến thức cơ bản về các thành phần kiến trúc. Trong lớp học lập trình này, bạn sẽ tổng hợp toàn bộ kiến thức và xử lý một mẫu nâng cao, đó là ứng dụng đặt bánh cupcake.
Bạn sẽ tìm hiểu cách sử dụng ViewModel
dùng chung để chia sẻ dữ liệu giữa các mảnh của cùng một hoạt động đồng thời tìm hiểu các khái niệm mới như phép biến đổi LiveData
.
Điều kiện tiên quyết
- Dễ dàng đọc hiểu bố cục Android dạng XML
- Quen thuộc với các khái niệm cơ bản về thành phần Điều hướng (Navigation) của Jetpack
- Có thể tạo biểu đồ điều hướng thể hiện đích đến của mảnh trong ứng dụng
- Từng sử dụng các mảnh trong một hoạt động
- Có thể tạo
ViewModel
để lưu trữ dữ liệu ứng dụng - Có thể sử dụng tính năng liên kết dữ liệu với
LiveData
để đảm bảo giao diện người dùng đồng bộ với dữ liệu ứng dụng trongViewModel
Kiến thức bạn sẽ học được
- Cách triển khai các phương pháp kiến trúc ứng dụng được đề xuất cho trường hợp sử dụng nâng cao hơn
- Cách sử dụng
ViewModel
dùng chung giữa các mảnh hoạt động - Cách áp dụng phép biến đổi
LiveData
Sản phẩm bạn sẽ tạo ra
- Ứng dụng Cupcake thể hiện quy trình đặt bánh cupcake, cho phép người dùng lựa chọn hương vị, số lượng và ngày lấy bánh.
Bạn cần có
- Một máy tính đã cài đặt Android Studio.
- Mã khởi động cho ứng dụng Cupcake.
2. Tổng quan về ứng dụng khởi động
Tổng quan về ứng dụng Cupcake
Ứng dụng đặt bánh cupcake cho thấy cách thiết kế và triển khai một ứng dụng đặt hàng trực tuyến. Kết thúc khoá học này, bạn sẽ có được ứng dụng Cupcake hoàn thiện với các màn hình sau. Người dùng có thể chọn số lượng, hương vị và các tuỳ chọn khác cho đơn đặt hàng bánh cupcake.
Tải mã khởi động cho lớp học lập trình này
Lớp học lập trình này sẽ cung cấp mã khởi động, cho phép bạn mở rộng các tính năng được dạy trong lớp học lập trình này. Mã khởi động có chứa mã mà bạn đã quen thuộc trong các lớp học lập trình trước đây.
Nếu tải mã khởi động xuống qua GitHub, hãy lưu ý tên thư mục của dự án là android-basics-kotlin-cupcake-app-starter
. Chọn thư mục này khi bạn mở dự án trong Android Studio.
Để lấy mã cho lớp học lập trình này và mở trong Android Studio, hãy thực hiện các bước sau.
Lấy mã
- Nhấp vào URL được cung cấp. Thao tác này sẽ mở trang GitHub của dự án trong một trình duyệt.
- Trên trang GitHub của dự án, hãy nhấp vào nút Code (Mã), một hộp thoại sẽ xuất hiện.
- Trong hộp thoại này, hãy 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.
- 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)).
- 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
- Khởi động Android Studio.
- 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 an existing Android Studio project (Mở một dự án hiện có trong Android Studio).
Lưu ý: Nếu Android Studio đã mở sẵn thì thay vào đó, hãy chọn tuỳ chọn sau đây trong trình đơn File > New > Import Project (Tệp > Mới > Nhập dự án).
- Trong hộp thoại Import Project (Nhập dự án), hãy chuyển đến nơi chứa thư mục dự án đã giải nén (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào thư mục dự án đó.
- Chờ Android Studio mở dự án.
- Nhấp vào nút Run (Chạy) để xây dựng và chạy ứng dụng. Hãy đảm bảo ứng dụng được dựng như mong đợi.
- Duyệt qua các tệp dự án trong cửa sổ công cụ Project (Dự án) để xem cách ứng dụng được thiết lập.
Tìm hiểu mã khởi động
- Mở dự án đã tải xuống trong Android Studio. Tên thư mục của dự án là
android-basics-kotlin-cupcake-app-starter
. Sau đó, hãy chạy ứng dụng. - Duyệt qua các tệp để tìm hiểu mã khởi động. Đối với các tệp bố cục, bạn có thể sử dụng tuỳ chọn Split (Chia tách) ở góc trên cùng bên phải để cùng lúc xem trước bố cục và mã XML.
- Khi biên dịch và chạy ứng dụng, bạn sẽ nhận thấy ứng dụng chưa hoàn thiện. Các nút trên ứng dụng chưa có nhiều tác dụng (ngoại trừ việc hiện thông báo
Toast
), đồng thời bạn không thể di chuyển đến các mảnh khác.
Sau đây là hướng dẫn từng bước về các tệp quan trọng trong dự án.
MainActivity:
MainActivity
có mã tương tự với mã được tạo mặc định, dùng để thiết lập khung hiển thị nội dung của hoạt động dưới dạng activity_main.xml
. Mã này sử dụng một hàm khởi tạo có tham số AppCompatActivity(@LayoutRes int contentLayoutId)
với chức năng lấy một bố cục được tăng cường trong super.onCreate(savedInstanceState)
.
Mã trong lớp (class) MainActivity
class MainActivity : AppCompatActivity(R.layout.activity_main)
giống với mã sau đây khi sử dụng hàm khởi tạo mặc định AppCompatActivity
:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Bố cục (thư mục res/layout):
Thư mục tài nguyên layout
chứa các tệp bố cục mảnh (fragment layout) và hoạt động (activity). Đây là các tệp bố cục đơn giản và cú pháp XML cũng quen thuộc như bạn từng thấy trong các lớp học lập trình trước đây.
fragment_start.xml
là màn hình xuất hiện đầu tiên trên ứng dụng. Trên màn hình này có hình ảnh bánh cupcake và 3 nút (button) để chọn số lượng bánh cần đặt: 1 bánh cupcake, 6 bánh cupcake và 12 bánh cupcake.fragment_flavor.xml
cho thấy danh sách hương vị bánh cupcake dưới dạng các nút chọn (radio button) và một nút (button) Next (Tiếp theo).fragment_pickup.xml
cung cấp một tuỳ chọn để chọn ngày lấy hàng và nút Next (Tiếp theo) để chuyển đến màn hình tóm tắt đơn đặt hàng.fragment_summary.xml
thể hiện thông tin tóm tắt về đơn đặt hàng, chẳng hạn như số lượng, hương vị và nút để gửi đơn đặt hàng đến một ứng dụng khác.
Lớp mảnh (fragment class):
StartFragment.kt
là màn hình xuất hiện đầu tiên trong ứng dụng. Lớp này chứa mã liên kết khung hiển thị (view binding code) và một trình xử lý lượt nhấp (click handler) cho 3 nút.- Các lớp
FlavorFragment.kt
,PickupFragment.kt
vàSummaryFragment.kt
chủ yếu chứa mã nguyên mẫu và trình xử lý lượt nhấp cho nút Next (Tiếp theo) hoặc Send Order to Another App (Gửi đơn đặt hàng đến một ứng dụng khác) để cho thấy các thông báo ngắn (toast message).
Tài nguyên (thư mục res):
- Thư mục
drawable
chứa tài nguyên để hiện chiếc bánh cupcake trên màn hình đầu tiên của ứng dụng cũng như chứa các tệp biểu tượng trình chạy (launcher icon). navigation/nav_graph.xml
chứa 4 đích đến cho các mảnh (startFragment
,flavorFragment
,pickupFragment
vàsummaryFragment
) không có Actions (Thao tác). Đối tượng này sẽ được định nghĩa ở các phần sau này trong lớp học lập trình này.- Thư mục
values
chứa thông tin về màu sắc, kích thước, chuỗi, kiểu định dạng và giao diện (theme) dùng để tuỳ chỉnh giao diện ứng dụng. Chắc là bạn đã quen với những loại tài nguyên này trong các lớp học lập trình trước đây.
3. Hoàn thiện Biểu đồ điều hướng (Navigation Graph)
Trong nhiệm vụ này, bạn sẽ kết nối các màn hình của ứng dụng Cupcake với nhau và hoàn thiện phần điều hướng ứng dụng.
Bạn còn nhớ chúng ta cần những gì để sử dụng thành phần Điều hướng (Navigation) không? Hãy làm theo hướng dẫn này để ôn lại cách thiết lập dự án và ứng dụng của bạn để:
- Sử dụng thư viện Jetpack Navigation
- Thêm
NavHost
vào hoạt động (activity) - Tạo biểu đồ điều hướng
- Thêm đích đến của mảnh vào biểu đồ điều hướng
Kết nối các đích đến trong biểu đồ điều hướng
- Trong Android Studio, trong cửa sổ Project (Dự án), hãy mở tệp res > navigation > nav_map.xml. Chuyển sang thẻ Design (Thiết kế) nếu chưa chọn.
- Thao tác này sẽ mở ra Navigation Editor (Trình chỉnh sửa chế độ điều hướng) để thể hiện biểu đồ điều hướng cho ứng dụng của bạn. Bạn sẽ thấy ứng dụng có sẵn 4 mảnh.
- Kết nối các đích đến của mảnh trong biểu đồ điều hướng. Tạo một thao tác từ
startFragment
đếnflavorFragment
, một kết nối từflavorFragment
đếnpickupFragment
và một kết nối từpickupFragment
đếnsummaryFragment
. Làm theo các bước tiếp theo nếu bạn cần hướng dẫn chi tiết hơn. - Di chuột qua mảnh startFragment cho đến khi bạn thấy đường viền màu xám xung quanh mảnh này và vòng tròn màu xám xuất hiện ở điểm giữa cạnh bên phải mảnh. Nhấp vào vòng tròn đó, kéo đến mảnh flavorFragment rồi nhả chuột.
- Mũi tên giữa hai mảnh cho biết kết nối được thiết lập thành công, nghĩa là bạn có thể điều hướng từ startFragment đến flavorFragment. Đây được gọi là Thao tác điều hướng (Navigation action), một thao tác mà bạn từng tìm hiểu trong một lớp học lập trình trước đây.
- Tương tự như vậy, hãy thêm các thao tác điều hướng từ flavorFragment đến pickupFragment và từ pickupFragment đến summaryFragment. Khi tạo xong các thao tác điều hướng, biểu đồ điều hướng hoàn thiện sẽ có dạng như sau.
- Ba thao tác bạn mới tạo cũng phải được phản ánh trong ngăn Component Tree (Cây thành phần).
- Khi định nghĩa biểu đồ điều hướng, bạn cũng nên chỉ định đích đến bắt đầu (start destination). Hiện tại, bạn có thể thấy startFragment có biểu tượng ngôi nhà nhỏ bên cạnh.
Tức là startfragment sẽ là mảnh xuất hiện đầu tiên trong NavHost
. Hãy xem đây là hành vi chúng ta muốn ứng dụng thực hiện. Về sau, bạn luôn có thể thay đổi đích bắt đầu bằng cách nhấp chuột phải vào một mảnh rồi chọn tuỳ chọn trình đơn Set as Start Destination (Đặt làm đích đến bắt đầu).
Điều hướng từ mảnh bắt đầu sang mảnh hương vị
Tiếp theo, bạn sẽ thêm mã để điều hướng từ startFragment đến flavorFragment bằng cách nhấn vào các nút trong mảnh đầu tiên, thay vì hiện thông báo Toast
. Dưới đây là mã tham khảo cho bố cục mảnh bắt đầu (start fragment). Bạn sẽ chuyển số lượng bánh cupcake đến mảnh hương vị (flavor fragment) trong nhiệm vụ tiếp theo.
- Trong cửa sổ Project (Dự án), hãy mở tệp Kotlin trong app > robots > com.example.cake > StartFragment
- Trong phương thức
onViewCreated()
, hãy lưu ý rằng trình nghe lượt nhấp được đặt trên 3 nút. Khi mỗi nút được nhấn, phương thứcorderCupcake()
sẽ được gọi với tham số là số lượng bánh cupcake (1, 6 hoặc 12 bánh cupcake).
Mã tham chiếu:
orderOneCupcake.setOnClickListener { orderCupcake(1) }
orderSixCupcakes.setOnClickListener { orderCupcake(6) }
orderTwelveCupcakes.setOnClickListener { orderCupcake(12) }
- Trong phương thức
orderCupcake()
, hãy thay thế mã để hiện thông báo ngắn bằng mã để điều hướng đến mảnh hương vị. Tìm phương thứcNavController
thông qua phương thứcfindNavController()
rồi gọinavigate()
trên phương thức này, với tham số truyền vào là mã nhận dạng thao tácR.id.action_startFragment_to_flavorFragment
. Đảm bảo rằng mã nhận dạng thao tác này khớp với thao tác bạn đã khai báo trongnav_graph.xml.
Thay thế
fun orderCupcake(quantity: Int) {
Toast.makeText(activity, "Ordered $quantity cupcake(s)", Toast.LENGTH_SHORT).show()
}
bằng
fun orderCupcake(quantity: Int) {
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Thêm lệnh
import
androidx.navigation.fragment.findNavController
hoặc bạn có thể chọn trong số các tuỳ chọn mà Android Studio đưa ra.
Thêm thành phần Điều hướng (Navigation) vào các mảnh hương vị và lấy hàng
Tương tự như nhiệm vụ trước, trong nhiệm vụ này, bạn sẽ thêm thành phần điều hướng vào các mảnh: hương vị và lấy hàng.
- Mở app > java > com.example.cupcake > FlavorFragment.kt. Hãy lưu ý rằng phương thức được gọi trong trình nghe lượt nhấp trên nút Next (Tiếp theo) là phương thức
goToNextScreen()
. - Trong tệp
FlavorFragment.kt
, bên trong phương thứcgoToNextScreen()
, hãy thay thế mã cho thấy thông báo ngắn để điều hướng đến mảnh lấy hàng (pickup fragment). Sử dụng mã nhận dạng thao tácR.id.action_flavorFragment_to_pickupFragment
. Hãy nhớ rằng mã này phải khớp với thao tác bạn đã khai báo trongnav_graph.xml.
fun goToNextScreen() {
findNavController().navigate(R.id.action_flavorFragment_to_pickupFragment)
}
Hãy nhớ import androidx.navigation.fragment.findNavController
.
- Tương tự như
PickupFragment.kt
, bên trong phương thứcgoToNextScreen()
, hãy thay thế mã hiện tại để điều hướng đến mảnh tóm tắt (summary fragment).
fun goToNextScreen() {
findNavController().navigate(R.id.action_pickupFragment_to_summaryFragment)
}
Nhập androidx.navigation.fragment.findNavController
.
- Chạy ứng dụng. Đảm bảo các nút hoạt động đúng khi di chuyển giữa các màn hình. Thông tin hiện trên mỗi mảnh có thể chưa đầy đủ. Đừng lo, bạn sẽ điền dữ liệu chính xác vào các mảnh đó trong các bước tiếp theo.
Cập nhật tiêu đề trong thanh ứng dụng
Khi bạn thao tác trong ứng dụng, hãy chú ý đến tiêu đề trên thanh ứng dụng. Bạn luôn thấy tiêu đề Cupcake.
Để cải thiện trải nghiệm người dùng, bạn nên cung cấp tiêu đề phù hợp hơn dựa trên chức năng của mảnh hiện tại.
Thay đổi tiêu đề trong thanh ứng dụng (còn gọi là thanh thao tác) cho từng mảnh bằng cách sử dụng NavController
và hiện nút Up (Mũi tên lên) (←).
- Trong
MainActivity.kt
, hãy ghi đè phương thứconCreate()
để thiết lập bộ điều khiển điều hướng. Tạo thực thể của lớpNavController
quaNavHostFragment
. - Gọi hàm
setupActionBarWithNavController(navController)
với tham số truyền vào là thực thể củaNavController
. Thao tác này sẽ thực hiện những việc sau: Hiện tiêu đề trong thanh ứng dụng dựa trên nhãn của đích đến và hiện nút Up (Mũi tên lên) mỗi khi bạn không ở đích đến trên cùng.
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
setupActionBarWithNavController(navController)
}
}
- Thêm các lệnh nhập cần thiết theo gợi ý của Android Studio.
import android.os.Bundle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
- Đặt tiêu đề thanh ứng dụng cho mỗi mảnh. Mở
navigation/nav_graph.xml
rồi chuyển sang thẻ Code (Mã). - Trong tệp
nav_graph.xml
, hãy chỉnh sửa thuộc tínhandroid:label
cho từng đích đến của mảnh. Sử dụng các tài nguyên chuỗi đã được khai báo trong ứng dụng khởi động như dưới đây.
Với mảnh bắt đầu (start fragment), hãy sử dụng @string/app_name
với giá trị Cupcake
.
Với mảnh hương vị (flavor fragment), hãy sử dụng @string/choose_flavor
với giá trị Choose Flavor
.
Với mảnh lấy hàng (pickup fragment), hãy sử dụng @string/choose_pickup_date
với giá trị Choose Pickup Date
.
Với mảnh tóm tắt (summary fragment), hãy sử dụng @string/order_summary
với giá trị Order Summary
.
<navigation ...>
<fragment
android:id="@+id/startFragment"
...
android:label="@string/app_name" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/flavorFragment"
...
android:label="@string/choose_flavor" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/pickupFragment"
...
android:label="@string/choose_pickup_date" ... >
<action ... />
</fragment>
<fragment
android:id="@+id/summaryFragment"
...
android:label="@string/order_summary" ... />
</navigation>
- Chạy ứng dụng. Lưu ý rằng tiêu đề trong thanh ứng dụng sẽ thay đổi khi bạn di chuyển đến từng đích đến của mảnh. Ngoài ra, hãy lưu ý rằng giờ đây nút Up (Trước) (mũi tên ←) đang xuất hiện trong thanh ứng dụng. Nếu bạn nhấn vào nút này, sẽ không có điều gì xảy ra. Bạn sẽ triển khai hành vi của nút Up (Trước) trong lớp học lập trình tiếp theo.
4. Tạo ViewModel dùng chung
Hãy chuyển sang phần điền dữ liệu chính xác cho từng mảnh. Bạn sẽ sử dụng một ViewModel
dùng chung để lưu dữ liệu của ứng dụng vào một ViewModel
duy nhất. Nhiều mảnh trong ứng dụng sẽ truy cập ViewModel
dùng chung này theo phạm vi hoạt động của các mảnh đó.
Đây là một cách dùng phổ biến nhằm chia sẻ dữ liệu giữa các mảnh trong hầu hết ứng dụng sản xuất. Ví dụ: trong phiên bản cuối cùng (của lớp học lập trình này) cho ứng dụng Cupcake (xem ảnh chụp màn hình bên dưới), người dùng chọn số lượng bánh cupcake trong màn hình đầu tiên. Trong màn hình thứ hai, giá tiền sẽ được tính và hiển thị dựa trên số lượng bánh đã chọn. Tương tự như vậy, các dữ liệu khác của ứng dụng như dữ liệu về hương vị và ngày lấy hàng sẽ được sử dụng trong màn hình tóm tắt đơn đặt hàng.
Sau khi xem xét các tính năng của ứng dụng, bạn sẽ thấy rằng thông tin đặt hàng nên được lưu trữ trong một ViewModel
duy nhất, để có thể được chia sẻ giữa các mảnh trong hoạt động này. Hãy nhớ rằng ViewModel
là một phần của Bộ thành phần cấu trúc Android. Dữ liệu ứng dụng lưu trong ViewModel
được lưu giữ lại trong quá trình thay đổi cấu hình. Để thêm ViewModel
vào ứng dụng, bạn cần tạo một lớp mới được mở rộng từ lớp ViewModel
.
Tạo OrderViewModel
Trong nhiệm vụ này, bạn sẽ tạo một ViewModel
dùng chung cho ứng dụng Cupcake với tên gọi OrderViewModel
. Đồng thời, bạn sẽ thêm dữ liệu ứng dụng dưới dạng các thuộc tính bên trong ViewModel
cũng như thêm các phương thức để cập nhật và chỉnh sửa dữ liệu. Sau đây là các thuộc tính của lớp:
- Số lượng đặt hàng (
Integer
) - Hương vị bánh cupcake (
String
) - Ngày lấy hàng (
String
) - Giá (
Double
)
Làm theo các phương pháp hay nhất về ViewModel
Trong ViewModel
, bạn không nên khai báo dữ liệu mô hình xem (mô hình xem) dưới dạng biến public
. Nếu không, các lớp bên ngoài có thể sửa đổi dữ liệu ứng dụng theo cách bất thường mà từ đó phát sinh các tình huống hiếm gặp không mong đợi trong ứng dụng. Thay vào đó, hãy đặt các thuộc tính có thể thay đổi (mutable) này dưới dạng private
, triển khai thuộc tính dự phòng và hiện phiên bản bất biến cho mỗi thuộc tính dưới dạng public
(nếu cần). Theo quy ước, cần sử dụng dấu gạch dưới (_
) trước tên của các thuộc tính có thể biến đổi private
.
Sau đây là các phương thức để cập nhật các thuộc tính trên, tuỳ thuộc vào lựa chọn của người dùng:
setQuantity(numberCupcakes: Int)
setFlavor(desiredFlavor: String)
setDate(pickupDate: String)
Bạn không cần phương thức setter cho phần giá cả vì giá sẽ được tính trong OrderViewModel
bằng các thuộc tính khác. Dưới đây là các bước hướng dẫn cách triển khai ViewModel
dùng chung.
Bạn sẽ tạo một gói mới trong dự án có tên model
và bổ sung lớp OrderViewModel
. Thao tác này sẽ giúp tách biệt mã của mô hình xem với phần còn lại của mã giao diện người dùng (các mảnh và hoạt động). Phương pháp lập trình hay nhất là tách mã thành các gói theo chức năng.
- Trong cửa sổ Project (Dự án) của Android Studio, hãy nhấp chuột phải vào com.example.cake > New (Mới) > Package (Gói).
- Hộp thoại New Package (Gói mới) sẽ xuất hiện, hãy đặt tên gói là
com.example.cupcake.model
.
- Tạo lớp Kotlin
OrderViewModel
trong góimodel
. Trong cửa sổ Project (Dự án), hãy nhấp chuột phải vào góimodel
rồi chọn New > Kotlin File/Class (Mới > Lớp/Tệp Kotlin). Trong hộp thoại mới này, hãy đặt tên tệp làOrderViewModel
.
- Trong tệp
OrderViewModel.kt
, hãy thay đổi chữ ký của lớp để mở rộngViewModel
.
import androidx.lifecycle.ViewModel
class OrderViewModel : ViewModel() {
}
- Bên trong lớp
OrderViewModel
, hãy thêm các thuộc tính đã thảo luận ở trên dưới dạngprivate
val
. - Thay đổi loại thuộc tính thành
LiveData
và bổ sung các trường dự phòng để có thể quan sát các thuộc tính này và cập nhật giao diện người dùng khi dữ liệu nguồn trong mô hình hiển thị thay đổi.
private val _quantity = MutableLiveData<Int>(0)
val quantity: LiveData<Int> = _quantity
private val _flavor = MutableLiveData<String>("")
val flavor: LiveData<String> = _flavor
private val _date = MutableLiveData<String>("")
val date: LiveData<String> = _date
private val _price = MutableLiveData<Double>(0.0)
val price: LiveData<Double> = _price
Bạn sẽ cần nhập các lớp sau:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
- Trong lớp
OrderViewModel
, hãy thêm các phương thức đã thảo luận ở trên. Bên trong các phương thức này, hãy gán đối số được truyền vào cho các thuộc tính có thể biến đổi. - Do các phương thức setter này cần được gọi từ bên ngoài mô hình hiển thị, nên bạn hãy giữ nguyên các phương thức này dưới dạng
public
(tức là không cầnprivate
hoặc đối tượng sửa đổi chế độ hiển thị khác trước từ khoáfun
). Chỉ định chế độ hiển thị mặc định trong Kotlin làpublic
.
fun setQuantity(numberCupcakes: Int) {
_quantity.value = numberCupcakes
}
fun setFlavor(desiredFlavor: String) {
_flavor.value = desiredFlavor
}
fun setDate(pickupDate: String) {
_date.value = pickupDate
}
- Tạo và chạy ứng dụng để đảm bảo không gặp vấn đề về lỗi biên dịch. Hiện chưa có thay đổi nào trên giao diện người dùng.
Bạn làm tốt lắm! Bây giờ bạn đã có điểm khởi đầu cho mô hình xem. Bạn sẽ dần thêm vào lớp này các thuộc tính và phương thức mà bạn nhận thấy là cần thiết để tạo thêm tính năng cho ứng dụng.
Nếu bạn thấy tên lớp (class), tên thuộc tính (property) hoặc tên phương thức (method) có phông chữ màu xám trong Android Studio, thì đó là lỗi đã lường trước. Tức là lớp, thuộc tính hoặc phương thức đó hiện chưa được sử dụng, những sẽ được sử dụng về sau! Nội dung này sẽ được đề cập trong phần tiếp theo.
5. Sử dụng ViewModel để cập nhật giao diện người dùng
Trong nhiệm vụ này, bạn sẽ sử dụng mô hình xem dùng chung đã tạo để cập nhật giao diện người dùng của ứng dụng. Điểm khác biệt chính trong việc triển khai mô hình xem dùng chung là cách truy cập mô hình xem qua bộ điều khiển giao diện người dùng. Bạn sẽ sử dụng thực thể của hoạt động thay vì thực thể của mảnh và sẽ tìm hiểu cách thực hiện điều này trong các phần tiếp theo.
Như vậy tác là bạn có thể chia sẻ mô hình xem giữa các mảnh. Mỗi mảnh đều có thể truy cập vào mô hình xem để kiểm tra thông tin của đơn đặt hàng hoặc cập nhật một số dữ liệu trong mô hình xem đó.
Cập nhật StartFragment để sử dụng mô hình xem
Để sử dụng mô hình xem dùng chung trong StartFragment
, bạn sẽ khởi động OrderViewModel
bằng cách sử dụng activityViewModels()
thay vì dùng lớp uỷ quyền (delegate class) viewModels()
.
viewModels()
cung cấp cho bạn thực thể củaViewModel
trong phạm vi mảnh hiện tại. Thực thể này thay đổi tuỳ theo mảnh.activityViewModels()
cung cấp cho bạn thực thể củaViewModel
trong phạm vi hoạt động hiện tại. Do đó, thực thể này sẽ giống nhau giữa nhiều mảnh trong cùng một hoạt động.
Sử dụng tính năng uỷ quyền thuộc tính của Kotlin
Trong Kotlin, mỗi thuộc tính có thể thay đổi (var
) đều có các hàm getter và setter mặc định được tạo tự động cho thuộc tính đó. Hàm setter và getter được gọi khi bạn gán giá trị hoặc đọc giá trị của thuộc tính. (Đối với thuộc tính chỉ đọc (val
), chỉ có hàm getter được tạo mặc định. Hàm getter này được gọi khi bạn đọc giá trị của thuộc tính chỉ đọc.)
Tính năng uỷ quyền thuộc tính trong Kotlin sẽ giúp bạn chuyển giao nhiệm vụ của hàm getter-setter cho một lớp khác.
Lớp này (được gọi là lớp uỷ quyền (delegate class)) cung cấp các hàm getter và setter của thuộc tính đó và xử lý các thay đổi trên thuộc tính đó.
Một thuộc tính uỷ quyền được xác định bằng mệnh đề by
và một thực thể của lớp uỷ quyền:
// Syntax for property delegation
var <property-name> : <property-type> by <delegate-class>()
- Trong lớp
StartFragment
, hãy tham chiếu đến mô hình xem dùng chung dưới dạng một biến của lớp. Sử dụng đối tượng uỷ quyền thuộc tínhby activityViewModels()
Kotlin trong thư việnfragment-ktx
.
private val sharedViewModel: OrderViewModel by activityViewModels()
Bạn có thể cần các lệnh nhập sau:
import androidx.fragment.app.activityViewModels
import com.example.cupcake.model.OrderViewModel
- Lặp lại bước trên cho các lớp
FlavorFragment
,PickupFragment
vàSummaryFragment
. Bạn sẽ sử dụng thực thể củasharedViewModel
này trong các phần sau của lớp học lập trình. - Trở lại với lớp
StartFragment
, bây giờ bạn có thể sử dụng mô hình xem. Ở phần đầu của phương thứcorderCupcake()
, hãy gọi phương thứcsetQuantity()
trong mô hình xem dùng chung để cập nhật số lượng trước khi chuyển đến mảnh chọn hương vị.
fun orderCupcake(quantity: Int) {
sharedViewModel.setQuantity(quantity)
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Trong lớp
OrderViewModel
, hãy thêm phương thức dưới đây để kiểm tra xem bạn đã thiết lập hương vị cho đơn đặt hàng hay chưa. Bạn sẽ sử dụng phương thức này trong lớpStartFragment
ở bước sau.
fun hasNoFlavorSet(): Boolean {
return _flavor.value.isNullOrEmpty()
}
- Trong lớp
StartFragment
, bên trong phương thứcorderCupcake()
, sau khi đặt số lượng, hãy đặt hương vị mặc định là Vanilla nếu chưa có hương vị nào được chọn, trước khi chuyển đến mảnh hương vị. Phương thức hoàn chỉnh của bạn sẽ có dạng như sau:
fun orderCupcake(quantity: Int) {
sharedViewModel.setQuantity(quantity)
if (sharedViewModel.hasNoFlavorSet()) {
sharedViewModel.setFlavor(getString(R.string.vanilla))
}
findNavController().navigate(R.id.action_startFragment_to_flavorFragment)
}
- Chạy ứng dụng để đảm bảo không có lỗi biên dịch. Tuy nhiên, sẽ chưa có thay đổi gì trên giao diện người dùng.
6. Dùng ViewModel để liên kết dữ liệu
Tiếp theo, bạn sẽ sử dụng tính năng liên kết dữ liệu để liên kết dữ liệu của mô hình xem với giao diện người dùng. Đồng thời, bạn sẽ cập nhật mô hình xem dùng chung dựa trên lựa chọn của người dùng trên giao diện ứng dụng.
Làm mới mối liên kết dữ liệu
Hãy lưu ý rằng Data Binding Library (Thư viện liên kết dữ liệu) là một phần của Android Jetpack. Cơ chế liên kết dữ liệu mang mục đích liên kết 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 đang sử dụng định dạng khai báo. 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ã). Việc thiết lập và cập nhật những mối liên kết này được thực hiện tự động, giúp giảm khả năng xảy ra lỗi nếu bạn quên cập nhật giao diện người dùng theo cách thủ công qua mã.
Cập nhật hương vị theo lựa chọn của người dùng
- Trong
layout/fragment_flavor.xml
, hãy thêm một thẻ<data>
vào trong thẻ<layout>
gốc. Thêm biến bố cục có tên làviewModel
thuộc loạicom.example.cupcake.model.OrderViewModel
. Đảm bảo tên gói trong thuộc tính type (loại) khớp với tên gói của lớp mô hình hiển thị dùng chungOrderViewModel
trong ứng dụng.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Tương tự như vậy, hãy lặp lại bước trên cho
fragment_pickup.xml
vàfragment_summary.xml
để thêm biến bố cụcviewModel
. Bạn sẽ sử dụng biến này trong các phần sau. Bạn không cần thêm mã này trongfragment_start.xml
vì bố cục này không sử dụng mô hình hiển thị dùng chung. - Trong lớp
FlavorFragment
, bên trongonViewCreated()
, hãy liên kết thực thể của mô hình xem này với thực thể mô hình xem dùng chung trong bố cục. Thêm mã dưới đây vào bên trong khốibinding?.
apply
.
binding?.apply {
viewModel = sharedViewModel
...
}
Áp dụng hàm phạm vi
Đây có thể là lần đầu tiên bạn thấy hàm apply
trong Kotlin. apply
là một hàm phạm vi trong thư viện Kotlin tiêu chuẩn. Hàm này thực thi một khối mã lệnh trong ngữ cảnh của một đối tượng. Hàm này tạo ra một phạm vi tạm thời và trong phạm vi đó, bạn có thể truy cập vào đối tượng mà không cần dùng tên. Cách sử dụng phổ biến cho hàm apply
là để định cấu hình một đối tượng. Những lệnh gọi như vậy có thể hiểu là "áp dụng các phép gán sau cho đối tượng".
Ví dụ:
clark.apply {
firstName = "Clark"
lastName = "James"
age = 18
}
// The equivalent code without apply scope function would look like the following.
clark.firstName = "Clark"
clark.lastName = "James"
clark.age = 18
- Lặp lại bước này cho phương thức
onViewCreated()
bên trong các lớpPickupFragment
vàSummaryFragment
.
binding?.apply {
viewModel = sharedViewModel
...
}
- Trong
fragment_flavor.xml
, hãy sử dụng biến bố cục mớiviewModel
để đặt thuộc tínhchecked
của các nút chọn dựa trên giá trịflavor
trong mô hình hiển thị này. Nếu hương vị thể hiện trên nút chọn giống với hương vị lưu trong mô hình xem, hãy cho thấy nút chọn ở trạng thái đã chọn (checked
= true). Biểu thức liên kết thể hiện trạng thái đã đánh dấu của VanillaRadioButton
sẽ có dạng như sau:
@{viewModel.flavor.equals(@string/vanilla)}
Về cơ bản, bạn đang so sánh thuộc tính viewModel.flavor
với tài nguyên chuỗi tương ứng thông qua hàm equals
để xác định xem trạng thái đã đánh dấu là true (đúng) hay false (sai).
<RadioGroup
...>
<RadioButton
android:id="@+id/vanilla"
...
android:checked="@{viewModel.flavor.equals(@string/vanilla)}"
.../>
<RadioButton
android:id="@+id/chocolate"
...
android:checked="@{viewModel.flavor.equals(@string/chocolate)}"
.../>
<RadioButton
android:id="@+id/red_velvet"
...
android:checked="@{viewModel.flavor.equals(@string/red_velvet)}"
.../>
<RadioButton
android:id="@+id/salted_caramel"
...
android:checked="@{viewModel.flavor.equals(@string/salted_caramel)}"
.../>
<RadioButton
android:id="@+id/coffee"
...
android:checked="@{viewModel.flavor.equals(@string/coffee)}"
.../>
</RadioGroup>
Liên kết trình nghe
Liên kết trình nghe (listener binding) là các biểu thức lambda sẽ chạy khi xảy ra một sự kiện, chẳng hạn như sự kiện onClick
. Các biểu thức này tương tự các tham chiếu phương thức như textview.setOnClickListener(clickListener)
nhưng tính năng liên kết trình nghe cho phép bạn chạy các biểu thức liên kết dữ liệu tuỳ ý.
- Trong
fragment_flavor.xml
, hãy thêm trình nghe sự kiện vào các nút chọn bằng cách sử dụng tính năng liên kết trình nghe. Sử dụng biểu thức lambda không có tham số rồi gọi phương thứcviewModel
.setFlavor()
với tham số truyền vào là tài nguyên chuỗi cho hương vị tương ứng.
<RadioGroup
...>
<RadioButton
android:id="@+id/vanilla"
...
android:onClick="@{() -> viewModel.setFlavor(@string/vanilla)}"
.../>
<RadioButton
android:id="@+id/chocolate"
...
android:onClick="@{() -> viewModel.setFlavor(@string/chocolate)}"
.../>
<RadioButton
android:id="@+id/red_velvet"
...
android:onClick="@{() -> viewModel.setFlavor(@string/red_velvet)}"
.../>
<RadioButton
android:id="@+id/salted_caramel"
...
android:onClick="@{() -> viewModel.setFlavor(@string/salted_caramel)}"
.../>
<RadioButton
android:id="@+id/coffee"
...
android:onClick="@{() -> viewModel.setFlavor(@string/coffee)}"
.../>
</RadioGroup>
- Chạy ứng dụng và lưu ý về cách chọn mặc định hương vị Vanilla trong mảnh hương vị.
Tuyệt vời! Bây giờ, bạn có thể chuyển sang các mảnh tiếp theo.
7. Cập nhật các mảnh lấy hàng và tóm tắt để sử dụng mô hình xem
Khi di chuyển trong ứng dụng, bạn sẽ thấy rằng trong mảnh lấy hàng, các nhãn tuỳ chọn trong nút chọn đang để trống. Trong nhiệm vụ này, bạn sẽ tính ra 4 ngày người dùng có thể lấy hàng rồi cho thấy trong mảnh lấy hàng. Có nhiều cách để hiện ngày theo định dạng. Dưới đây là một số tiện ích hữu ích để định dạng mà Android cung cấp.
Tạo danh sách cách lấy hàng
Trình định dạng ngày
Nền tảng Android cung cấp một lớp có tên SimpleDateFormat
, dùng để định dạng và phân tích cú pháp ngày tháng phân biệt theo ngôn ngữ. Lớp này cho phép định dạng (ngày → văn bản) và phân tích cú pháp ngày tháng (văn bản → ngày).
Bạn có thể tạo một thực thể của lớp SimpleDateFormat
với tham số truyền vào là một chuỗi mẫu (pattern string) và miền bản địa (locale):
SimpleDateFormat("E MMM d", Locale.getDefault())
Một chuỗi mẫu như "E MMM d"
thể hiện định dạng Ngày và Giờ. Các chữ cái từ 'A'
đến 'Z'
và từ 'a'
đến 'z'
là chữ cái đại diện cho các thành phần của chuỗi ngày hoặc giờ. Ví dụ: d
đại diện cho ngày trong tháng, y
cho năm và M
cho tháng. Nếu ngày là ngày 4 tháng 1 năm 2018, chuỗi theo mẫu "EEE, MMM d"
sẽ được phân tích cú pháp thành "Wed, Jul 4"
. Để nắm được danh sách đầy đủ chữ cái mẫu, vui lòng xem tài liệu.
Đối tượng Locale
đại diện cho một khu vực địa lý, chính trị hoặc văn hoá cụ thể. Đối tượng này đại diện cho tổ hợp ngôn ngữ/quốc gia/biến thể. Ngôn ngữ được dùng để thay đổi cách trình bày thông tin, chẳng hạn như con số hoặc ngày cho phù hợp với quy ước trong khu vực. Ngày và giờ tuỳ theo miền bản địa vì mỗi khu vực trên thế giới lại có những cách viết ngày và giờ riêng. Bạn sẽ sử dụng phương thức Locale.getDefault()
để truy xuất thông tin ngôn ngữ được thiết lập trên thiết bị của người dùng và truyền thông tin đó vào hàm khởi tạo SimpleDateFormat
.
Miền bản địa (locale) trong Android là sự kết hợp giữa ngôn ngữ (language) và mã quốc gia (country code). Mã ngôn ngữ là mã số ngôn ngữ theo tiêu chuẩn ISO gồm hai chữ cái viết thường, chẳng hạn như "en" thể hiện tiếng Anh. Mã quốc gia là mã số quốc gia gồm hai chữ cái viết hoa theo tiêu chuẩn ISO, chẳng hạn như "US" thể hiện Hoa Kỳ.
Bây giờ hãy sử dụng SimpleDateFormat
và Locale
để xác định ngày có thể lấy hàng cho ứng dụng Cupcake.
- Trong lớp
OrderViewModel
, hãy thêm hàm dưới đây với tên gọigetPickupOptions()
để tạo và trả về danh sách ngày lấy hàng. Trong phương thức này, hãy tạo một biếnval
có tên làoptions
và khởi động biến đó quamutableListOf
<String>()
.
private fun getPickupOptions(): List<String> {
val options = mutableListOf<String>()
}
- Tạo một chuỗi định dạng sử dụng
SimpleDateFormat
với tham số truyền vào là chuỗi mẫu"E MMM d"
và ngôn ngữ. Trong chuỗi mẫu,E
là viết tắt tên ngày trong tuần và được phân tích cú pháp thành "Tue Dec 10".
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
Nhập java.text.SimpleDateFormat
và java.util.Locale
khi Android Studio nhắc.
- Lấy thực thể
Calendar
rồi gán cho một biến mới. Khai báo biến này là mộtval
. Biến này sẽ chứa ngày và giờ hiện tại. Đồng thời, hãy nhậpjava.util.Calendar
.
val calendar = Calendar.getInstance()
- Xây dựng danh sách ngày bắt đầu từ ngày hiện tại và 3 ngày tiếp theo. Do bạn sẽ cần 4 tuỳ chọn ngày lấy hàng, hãy lặp lại khối mã này 4 lần. Khối
repeat
này sẽ định dạng một ngày, thêm ngày đó vào danh sách tuỳ chọn ngày, sau đó chuyển đến ngày dương lịch tiếp theo.
repeat(4) {
options.add(formatter.format(calendar.time))
calendar.add(Calendar.DATE, 1)
}
- Trả về
options
đã cập nhật ở cuối phương thức. Sau đây là phương thức hoàn chỉnh:
private fun getPickupOptions(): List<String> {
val options = mutableListOf<String>()
val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
val calendar = Calendar.getInstance()
// Create a list of dates starting with the current date and the following 3 dates
repeat(4) {
options.add(formatter.format(calendar.time))
calendar.add(Calendar.DATE, 1)
}
return options
}
- Trong lớp
OrderViewModel
, hãy thêm thuộc tính lớp có têndateOptions
và là mộtval
. Hãy khởi động biến này bằng phương thứcgetPickupOptions()
bạn vừa tạo.
val dateOptions = getPickupOptions()
Cập nhật bố cục để hiện các cách lấy hàng
Giờ đây, bạn đã có 4 ngày có thể lấy hàng trong mô hình xem, hãy cập nhật bố cục fragment_pickup.xml
để cho thấy những ngày này. Bạn cũng sẽ sử dụng tính năng liên kết dữ liệu để hiện trạng thái của mỗi nút chọn cũng như để cập nhật ngày trong mô hình xem khi lựa chọn nút chọn khác. Cách triển khai này tương tự như việc liên kết dữ liệu trong mảnh hương vị.
Trong fragment_pickup.xml
:
Nút chọn option0
thể hiện dateOptions[0]
trong viewModel
(hôm nay)
Nút chọn option1
thể hiện dateOptions[1]
trong viewModel
(ngày mai)
Nút chọn option2
thể hiện dateOptions[2]
trong viewModel
(ngày kia)
Nút chọn option3
thể hiện dateOptions[3]
trong viewModel
(3 ngày sau)
- Trong
fragment_pickup.xml
, đối với nút chọnoption0
, hãy sử dụng biến bố cục mớiviewModel
để thiết lập thuộc tínhchecked
dựa trên giá trịdate
trong mô hình hiển thị. So sánh thuộc tínhviewModel.date
với chuỗi đầu tiên trong danh sáchdateOptions
. Đây chính là ngày hiện tại. Sử dụng hàmequals
để so sánh và biểu thức liên kết cuối cùng sẽ có dạng như sau:
@{viewModel.date.equals(viewModel.dateOptions[0])}
- Đối với cùng một nút chọn, hãy thêm trình nghe sự kiện bằng cách sử dụng tính năng liên kết trình nghe cho thuộc tính
onClick
. Khi nhấp vào tuỳ chọn của nút chọn này, hãy gọi hàmsetDate()
trênviewModel
, truyền vào tham sốdateOptions[0]
. - Đối với cùng một nút chọn, hãy thiết lập giá trị thuộc tính
text
là chuỗi đầu tiên trong danh sáchdateOptions
.
<RadioButton
android:id="@+id/option0"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[0])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[0])}"
android:text="@{viewModel.dateOptions[0]}"
...
/>
- Lặp lại các bước trên cho các nút chọn khác, sau đó thay đổi chỉ mục của
dateOptions
cho phù hợp.
<RadioButton
android:id="@+id/option1"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[1])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[1])}"
android:text="@{viewModel.dateOptions[1]}"
... />
<RadioButton
android:id="@+id/option2"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[2])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[2])}"
android:text="@{viewModel.dateOptions[2]}"
... />
<RadioButton
android:id="@+id/option3"
...
android:checked="@{viewModel.date.equals(viewModel.dateOptions[3])}"
android:onClick="@{() -> viewModel.setDate(viewModel.dateOptions[3])}"
android:text="@{viewModel.dateOptions[3]}"
... />
- Chạy ứng dụng và bạn sẽ thấy các tuỳ chọn ngày lấy hàng trong vài ngày tới. Ảnh chụp màn hình của bạn sẽ thay đổi tuỳ vào ngày hiện tại trên thiết bị của bạn. Hãy lưu ý rằng hiện chưa thiết lập tuỳ chọn mặc định. Bạn sẽ thực hiện việc này trong bước tiếp theo.
- Trong lớp
OrderViewModel
, hãy tạo một hàm có tênresetOrder()
để thiết lập lại các thuộc tínhMutableLiveData
trong mô hình xem. Gán giá trị ngày hiện tại trong danh sáchdateOptions
cho_date.
value.
fun resetOrder() {
_quantity.value = 0
_flavor.value = ""
_date.value = dateOptions[0]
_price.value = 0.0
}
- Thêm một khối
init
vào lớp này và gọi phương thức mớiresetOrder()
qua đó.
init {
resetOrder()
}
- Xoá các giá trị ban đầu khỏi phần khai báo về các thuộc tính trong lớp. Bây giờ, bạn đang sử dụng khối
init
để khởi tạo các thuộc tính khi một thực thể của lớpOrderViewModel
được tạo ra.
private val _quantity = MutableLiveData<Int>()
val quantity: LiveData<Int> = _quantity
private val _flavor = MutableLiveData<String>()
val flavor: LiveData<String> = _flavor
private val _date = MutableLiveData<String>()
val date: LiveData<String> = _date
private val _price = MutableLiveData<Double>()
val price: LiveData<Double> = _price
- Chạy lại ứng dụng, lưu ý rằng ngày hôm nay sẽ được chọn theo mặc định.
Cập nhật mảnh tóm tắt để sử dụng mô hình xem
Tiếp theo, hãy chuyển đến mảnh cuối cùng. Mục đích của mảnh tóm tắt đơn đặt hàng là cho thấy thông tin tóm tắt về đơn đặt hàng. Trong nhiệm vụ này, bạn sẽ tận dụng toàn bộ thông tin đơn đặt hàng từ mô hình khung hiển thị dùng chung rồi cập nhật chi tiết đơn đặt hàng trên màn hình bằng tính năng liên kết dữ liệu.
- Trong
fragment_summary.xml
, hãy đảm bảo bạn đã khai báo biến dữ liệuviewModel
.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Trong
SummaryFragment
, trong hàmonViewCreated()
, hãy đảm bảobinding.viewModel
đã được khởi động. - Trên
fragment_summary.xml
, hãy đọc từ mô hình xem để cập nhật nội dung tóm tắt thông tin đơn đặt hàng trên màn hình. Cập nhật số lượng, hương vị và ngàyTextViews
bằng cách thêm các thuộc tính văn bản sau đây. Biến số lượng thuộc kiểuInt
nên bạn cần đổi biến này thành kiểu chuỗi.
<TextView
android:id="@+id/quantity"
...
android:text="@{viewModel.quantity.toString()}"
... />
<TextView
android:id="@+id/flavor"
...
android:text="@{viewModel.flavor}"
... />
<TextView
android:id="@+id/date"
...
android:text="@{viewModel.date}"
... />
- Chạy và kiểm thử ứng dụng để chắc chắn rằng các tuỳ chọn đơn đặt hàng đã chọn sẽ xuất hiện trong phần tóm tắt đơn đặt hàng.
8. Tính giá tiền theo chi tiết đơn hàng
Nhìn vào ảnh chụp màn hình cuối cùng của ứng dụng trong lớp học lập trình này, bạn sẽ thấy giá tiền đã thực sự hiện trên từng mảnh (ngoại trừ StartFragment
) để người dùng biết được giá tiền khi tạo đơn đặt hàng.
Sau đây là các quy tắc tính giá tiền của cửa hàng bánh cupcake.
- Mỗi bánh cupcake có giá 2 USD
- Lấy hàng trong ngày sẽ cộng thêm 3 USD
Do đó, với đơn đặt hàng gồm 6 bánh, giá tiền sẽ là 6 bánh x 2 USD mỗi bánh = 12 USD. Nếu người dùng muốn lấy hàng trong ngày thì cộng thêm phí 3 USD, tổng tiền sẽ là 15 USD.
Cập nhật giá tiền trong mô hình xem
Để hỗ trợ chức năng này trong ứng dụng, trước tiên, hãy xử lý giá cho mỗi bánh cupcake và tạm thời bỏ qua chi phí lấy hàng trong ngày.
- Mở
OrderViewModel.kt
rồi tạo một biến để lưu giá của mỗi chiếc bánh cupcake. Khai báo biến này là một biến hằng số riêng trên phần đầu tệp, bên ngoài định nghĩa lớp (nhưng sau các lệnh nhập). Sử dụng chỉ địnhconst
và thiết lập chế độ chỉ đọcval
.
package ...
import ...
private const val PRICE_PER_CUPCAKE = 2.00
class OrderViewModel : ViewModel() {
...
Hãy nhớ rằng giá trị hằng số (thể hiện bằng từ khoá const
trong Kotlin) sẽ không thay đổi và là giá trị xác định trong thời gian biên dịch. Để tìm hiểu thêm về hằng số, tham khảo tài liệu này.
- Bây giờ bạn đã xác định được giá của mỗi chiếc bánh cupcake, hãy tạo một phương thức trợ giúp để tính giá tiền. Phương thức này có thể là
private
vì chỉ dùng trong lớp này. Bạn sẽ thay đổi logic tính giá để tính cả phí lấy hàng trong ngày trong nhiệm vụ tiếp theo.
private fun updatePrice() {
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
}
Dòng lệnh này nhân giá bánh với số lượng bánh đã đặt. Đối với mã trong dấu ngoặc đơn, vì giá trị của quantity.value
có thể rỗng (null) nên bạn hãy sử dụng toán tử elvis (?:
). Với toán tử elvis (?:
), nếu biểu thức bên trái của toán tử này khác rỗng thì hãy sử dụng giá trị của biểu thức này. Ngược lại, nếu biểu thức ở bên trái là rỗng, hãy sử dụng biểu thức ở bên phải toán tử elvis (là 0
trong trường hợp này).
- Cũng trong lớp
OrderViewModel
, hãy cập nhật biến giá tiền với số lượng bánh được đặt. Gọi đến hàm mới này trong hàmsetQuantity()
.
fun setQuantity(numberCupcakes: Int) {
_quantity.value = numberCupcakes
updatePrice()
}
Liên kết thuộc tính giá tiền với giao diện người dùng
- Trong các bố cục của
fragment_flavor.xml
,fragment_pickup.xml
vàfragment_summary.xml
, hãy đảm bảo bạn đã định nghĩa biến dữ liệuviewModel
có kiểucom.example.cupcake.model.OrderViewModel
.
<layout ...>
<data>
<variable
name="viewModel"
type="com.example.cupcake.model.OrderViewModel" />
</data>
<ScrollView ...>
...
- Trong phương thức
onViewCreated()
của mỗi lớp mảnh (fragment class), hãy nhớ liên kết thực thể của đối tượng mô hình xem trong mảnh này với biến dữ liệu mô hình xem trong bố cục.
binding?.apply {
viewModel = sharedViewModel
...
}
- Trong mỗi bố cục mảnh, hãy sử dụng biến
viewModel
để thiết lập giá tiền thể hiện trong bố cục. Bắt đầu chỉnh sửa tệpfragment_flavor.xml
. Đối với khung hiển thị văn bảnsubtotal
, hãy đặt giá trị cho thuộc tínhandroid:text
là"@{@string/subtotal_price(viewModel.price)}".
Biểu thức bố cục liên kết dữ liệu này sử dụng tài nguyên chuỗi@string/subtotal_price
với tham số truyền vào là giá tiền trong mô hình hiển thị. Ví dụ: kết quả sẽ hiện Subtotal 12.0 (Đơn giá 12.0).
...
<TextView
android:id="@+id/subtotal"
android:text="@{@string/subtotal_price(viewModel.price)}"
... />
...
Bạn đang sử dụng tài nguyên chuỗi đã được khai báo trong tệp strings.xml
:
<string name="subtotal_price">Subtotal %s</string>
- Chạy ứng dụng. Nếu bạn chọn One cupcake (Một bánh cupcake) trong mảnh bắt đầu, thì mảnh hương vị sẽ cho thấy Subtotal 2.0 (Đơn giá 2.0). Nếu bạn chọn Six cupcakes (6 bánh cupcake), thì mảnh hương vị sẽ hiện Subtotal 12.0 (Đơn giá 12.0), v.v. Bạn sẽ định dạng giá tiền thành định dạng tiền tệ phù hợp trong các bước sau. Hiện tại, hành vi này đã được lường trước.
- Bây giờ, hãy thay đổi tương tự cho các mảnh lấy hàng và tóm tắt đơn đặt hàng. Trong các bố cục
fragment_pickup.xml
vàfragment_summary.xml
, hãy chỉnh sửa các chế độ xem văn bản để dùng cả thuộc tínhviewModel
price
.
fragment_pickup.xml
...
<TextView
android:id="@+id/subtotal"
...
android:text="@{@string/subtotal_price(viewModel.price)}"
... />
...
fragment_summary.xml
...
<TextView
android:id="@+id/total"
...
android:text="@{@string/total_price(viewModel.price)}"
... />
...
- Chạy ứng dụng. Hãy chắc chắn rằng giá tiền hiện trong phần tóm tắt đơn đặt hàng được tính toán chính xác tương ứng với số lượng bánh là 1, 6 và 12. Như đã đề cập ở trên, giá tiền hiện chưa được định dạng chính xác (hiện dưới dạng 2.0 thay vì 2 USD hoặc 12.0 thay vì 12 USD).
Tính thêm phí lấy hàng trong ngày
Trong nhiệm vụ này, bạn sẽ triển khai quy tắc thứ hai về việc tính giá tiền. Nếu lấy hàng trong ngày, đơn đặt hàng sẽ tính thêm 3 USD.
- Trong lớp
OrderViewModel
, hãy định nghĩa một hằng số riêng mới ở cấp cao nhất cho chi phí lấy hàng trong ngày.
private const val PRICE_FOR_SAME_DAY_PICKUP = 3.00
- Trong hàm
updatePrice()
, hãy kiểm tra xem người dùng có sử dụng tuỳ chọn lấy hàng trong ngày hay không. Kiểm tra xem ngày trong mô hình xem (_date.
value
) có giống với ngày đầu tiên trong danh sáchdateOptions
hay không. Ngày này luôn là ngày hiện tại.
private fun updatePrice() {
_price.value = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
if (dateOptions[0] == _date.value) {
}
}
- Để các bước tính toán trở nên đơn giản hơn, hãy tạo một biến tạm thời có tên
calculatedPrice
. Tính giá tiền mới nhất rồi gán lại cho_price.
value
.
private fun updatePrice() {
var calculatedPrice = (quantity.value ?: 0) * PRICE_PER_CUPCAKE
// If the user selected the first option (today) for pickup, add the surcharge
if (dateOptions[0] == _date.value) {
calculatedPrice += PRICE_FOR_SAME_DAY_PICKUP
}
_price.value = calculatedPrice
}
- Gọi phương thức trợ giúp
updatePrice()
qua phương thứcsetDate()
để thêm phí lấy hàng trong ngày.
fun setDate(pickupDate: String) {
_date.value = pickupDate
updatePrice()
}
- Chạy ứng dụng và di chuyển qua các bước trong ứng dụng. Bạn sẽ thấy rằng khi thay đổi ngày lấy hàng, phí lấy hàng trong ngày không được trừ đi trong tổng số tiền. Lý do là giá tiền đã thay đổi trong mô hình xem nhưng chưa được thông báo cho bố cục ràng buộc.
Thiết lập chủ sở hữu vòng đời (Lifecycle owner) để quan sát LiveData
LifecycleOwner
là một lớp có vòng đời trên Android, chẳng hạn như một hoạt động hoặc một mảnh. Trình quan sát (observer) LiveData
chỉ theo dõi thay đổi đối với dữ liệu của ứng dụng khi chủ sở hữu vòng đời ở trạng thái đang hoạt động (STARTED
hoặc RESUMED
).
Trong ứng dụng của bạn, đối tượng LiveData
hoặc dữ liệu quan sát được là thuộc tính price
mô hình xem. Chủ sở hữu vòng đời là các mảnh hương vị, lấy hàng và tóm tắt. Trình quan sát LiveData
là biểu thức liên kết trong tệp bố cục có dữ liệu quan sát được, chẳng hạn như giá tiền. Với tính năng Liên kết dữ liệu (Data Binding), khi một giá trị quan sát được thay đổi, các thành phần trên giao diện người dùng có liên kết với giá trị đó sẽ cập nhật tự động.
Ví dụ về biểu thức liên kết: android:text="@{@string/subtotal_price(viewModel.price)}"
Để các thành phần giao diện người dùng tự động cập nhật, bạn phải liên kết binding.
lifecycleOwner
với chủ sở hữu vòng đời trong ứng dụng. Bạn sẽ thực hiện việc này trong bước tiếp theo.
- Trong các lớp
FlavorFragment
,PickupFragment
vàSummaryFragment
, bên trong phương thứconViewCreated()
, hãy thêm nội dung sau vào khối lệnhbinding?.apply
. Thao tác này sẽ thiết lập chủ sở hữu vòng đời trên đối tượng liên kết. Việc thiết lập chủ sở hữu vòng đời sẽ cho phép ứng dụng quan sát các đối tượngLiveData
.
binding?.apply {
lifecycleOwner = viewLifecycleOwner
...
}
- Chạy lại ứng dụng. Trên màn hình lấy hàng, hãy thay đổi ngày lấy hàng và lưu ý điểm khác biệt trong cách giá tiền tự động thay đổi. Và khoản phí lấy hàng được thể hiện chính xác trong màn hình tóm tắt đơn đặt hàng.
- Lưu ý rằng khi chọn ngày lấy hàng là ngày hiện tại, giá tiền của đơn đặt hàng sẽ tăng thêm 3 USD. Nếu chọn một ngày bất kỳ trong tương lai, giá tiền vẫn được tính bằng số lượng bánh x 2 USD.
- Hãy kiểm thử nhiều trường hợp khác bằng cách thay đổi số lượng, hương vị và ngày lấy bánh. Bây giờ, bạn sẽ thấy giá tiền đã được cập nhật từ mô hình xem cho từng mảnh. Điều tuyệt vời nhất là bạn không cần viết thêm mã Kotlin để cập nhật giao diện người dùng mỗi khi giá tiền thay đổi.
Để hoàn thiện chức năng tính giá, bạn cần định dạng giá tiền theo nội tệ.
Dùng phép biến đổi LiveData để định dạng giá tiền
(Các) phương thức biến đổi LiveData
đem đến một cách để thao tác dữ liệu trên nguồn LiveData
và trả về một đối tượng LiveData
. Nói một cách đơn giản, phương thức này sẽ biến giá trị của LiveData
thành một giá trị khác. Các phép biến đổi này sẽ không được thực hiện trừ phi trình quan sát đang theo dõi đối tượng LiveData
.
Transformations.map()
là một trong các hàm biến đổi. Hàm này sẽ lấy nguồn LiveData
và một hàm khác làm tham số. Hàm này thao tác trên nguồn LiveData
và trả về một giá trị được cập nhật và cũng quan sát được.
Sau đây là một số ví dụ theo thời gian thực có thể sử dụng phép biến đổi LiveData:
- Định dạng cách thể hiện chuỗi ngày, giờ
- Sắp xếp các thành phần trong danh sách
- Lọc hoặc nhóm các thành phần
- Tính kết quả từ một danh sách, chẳng hạn như tổng của tất cả thành phần, số lượng thành phần, trả về thành phần cuối cùng, v.v.
Trong nhiệm vụ này, bạn sẽ sử dụng phương thức Transformations.map()
để định dạng giá tiền theo nội tệ. Bạn sẽ đổi giá gốc dưới dạng một số thập phân (LiveData<Double>
) thành một giá trị chuỗi (LiveData<String>
).
- Trong lớp
OrderViewModel
, hãy thay đổi kiểu thuộc tính dự phòng thànhLiveData<String>
thay vìLiveData<Double>.
Giá sau khi định dạng sẽ là một chuỗi có ký hiệu tiền tệ, chẳng hạn như "$". Bạn sẽ khắc phục lỗi khởi động trong bước tiếp theo.
private val _price = MutableLiveData<Double>()
val price: LiveData<String>
- Sử dụng
Transformations.map()
để khởi tạo biến mới, tham số truyền vào là_price
và một hàm lambda. Sử dụng phương thứcgetCurrencyInstance()
trong lớpNumberFormat
để đổi giá tiền sang định dạng nội tệ. Mã biến đổi có dạng như sau.
private val _price = MutableLiveData<Double>()
val price: LiveData<String> = Transformations.map(_price) {
NumberFormat.getCurrencyInstance().format(it)
}
Bạn cần nhập androidx.lifecycle.Transformations
và java.text.NumberFormat
.
- Chạy ứng dụng. Bây giờ, bạn sẽ thấy chuỗi giá tiền đã được định dạng cho phần đơn giá và thành tiền. Giao diện người dùng trở nên thân thiện hơn rất nhiều!
- Kiểm tra lại để đảm bảo tính năng này hoạt động như mong đợi. Kiểm thử các trường hợp như: Đặt 1 bánh, 6 bánh, 12 bánh. Hãy đảm bảo giá tiền đã được cập nhật chính xác trên mỗi màn hình. Giá tiền nên thể hiện Subtotal $2.00 (Đơn giá $2) trên các mảnh hương vị và lấy hàng, và Total $2.00 (Thành tiền $12) trên mảnh tóm tắt. Ngoài ra, hãy đảm bảo phần tóm tắt đơn đặt hàng thể hiện chính xác thông tin chi tiết về đơn đặt hàng.
9. Dùng tính năng liên kết trình nghe để thiết lập trình nghe lượt nhấp
Trong nhiệm vụ này, bạn sẽ sử dụng tính năng liên kết trình nghe để liên kết trình nghe lượt nhấp nút trong các lớp mảnh với bố cục.
- Trong tệp bố cục
fragment_start.xml
, hãy thêm biến dữ liệu có tên làstartFragment
thuộc kiểucom.example.cupcake.StartFragment
. Đảm bảo tên gói của mảnh khớp với tên gói của ứng dụng.
<layout ...>
<data>
<variable
name="startFragment"
type="com.example.cupcake.StartFragment" />
</data>
...
<ScrollView ...>
- Trong
StartFragment.kt
, trong phương thứconViewCreated()
, hãy liên kết biến dữ liệu mới vào thực thể của mảnh. Bạn có thể truy cập vào thực thể của mảnh bên trong mảnh đó bằng cách sử dụng từ khoáthis
. Xoá khốibinding?.
apply
cùng với mã trong đó. Phương thức hoàn chỉnh sẽ có dạng như sau.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.startFragment = this
}
- Trong
fragment_start.xml
, hãy thêm trình nghe sự kiện bằng cách sử dụng tính năng liên kết trình nghe vào thuộc tínhonClick
của các nút và gọi hàmorderCupcake()
trênstartFragment
, với tham số là số lượng bánh.
<Button
android:id="@+id/order_one_cupcake"
android:onClick="@{() -> startFragment.orderCupcake(1)}"
... />
<Button
android:id="@+id/order_six_cupcakes"
android:onClick="@{() -> startFragment.orderCupcake(6)}"
... />
<Button
android:id="@+id/order_twelve_cupcakes"
android:onClick="@{() -> startFragment.orderCupcake(12)}"
... />
- Chạy ứng dụng. Hãy lưu ý rằng trình xử lý lượt nhấp nút trong mảnh bắt đầu đang hoạt động như dự kiến.
- Tương tự, hãy thêm các biến dữ liệu ở trên trong các bố cục khác để liên kết thực thể của mảnh,
fragment_flavor.xml
,fragment_pickup.xml
vàfragment_summary.xml
.
Trong fragment_flavor.xml
<layout ...>
<data>
<variable
... />
<variable
name="flavorFragment"
type="com.example.cupcake.FlavorFragment" />
</data>
<ScrollView ...>
Trong fragment_pickup.xml
:
<layout ...>
<data>
<variable
... />
<variable
name="pickupFragment"
type="com.example.cupcake.PickupFragment" />
</data>
<ScrollView ...>
Trong fragment_summary.xml
:
<layout ...>
<data>
<variable
... />
<variable
name="summaryFragment"
type="com.example.cupcake.SummaryFragment" />
</data>
<ScrollView ...>
- Trong các lớp mảnh còn lại, trong phương thức
onViewCreated()
, hãy xoá mã thiết lập trình nghe lượt nhấp trên các nút theo cách thủ công. - Trong phương thức
onViewCreated()
, hãy liên kết biến dữ liệu mảnh với thực thể của mảnh đó. Ở đây, bạn sẽ sử dụng từ khoáthis
theo một cách khác. Lý do là vì bên trong khốibinding?.apply
, từ khoáthis
tham chiếu đến thực thể liên kết chứ không tham chiếu đến thực thể mảnh. Sử dụng@
và chỉ định rõ tên lớp mảnh, ví dụ:this@FlavorFragment
. Các phương thứconViewCreated()
đã hoàn chỉnh sẽ có dạng như sau:
Phương thức onViewCreated()
trong lớp FlavorFragment
sẽ có dạng như sau:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
flavorFragment = this@FlavorFragment
}
}
Phương thức onViewCreated()
trong lớp PickupFragment
sẽ có dạng như sau:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
pickupFragment = this@PickupFragment
}
}
Phương thức onViewCreated()
thu được trong phương thức lớp SummaryFragment
sẽ có dạng như sau:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
summaryFragment = this@SummaryFragment
}
}
- Tương tự như vậy, trong các tệp bố cục khác, hãy thêm biểu thức liên kết trình nghe vào thuộc tính
onClick
của các nút.
Trong fragment_flavor.xml
:
<Button
android:id="@+id/next_button"
android:onClick="@{() -> flavorFragment.goToNextScreen()}"
... />
Trong fragment_pickup.xml
:
<Button
android:id="@+id/next_button"
android:onClick="@{() -> pickupFragment.goToNextScreen()}"
... />
Trong fragment_summary.xml
:
<Button
android:id="@+id/send_button"
android:onClick="@{() -> summaryFragment.sendOrder()}"
...>
- Chạy ứng dụng để chắc chắn rằng các nút vẫn hoạt động như dự kiến. Có thể thấy rằng không có sự thay đổi rõ ràng nào về hành vi, nhưng hiện giờ bạn đã sử dụng được tính năng liên kết trình nghe để thiết lập trình nghe lượt nhấp!
Chúc mừng bạn đã hoàn thành lớp học lập trình này và xây dựng được ứng dụng Cupcake! Tuy nhiên, ứng dụng vẫn chưa hoàn chỉnh. Trong lớp học lập trình tiếp theo, bạn sẽ thêm nút Cancel (Huỷ) và chỉnh sửa ngăn xếp quay lại (backstack). Bạn cũng sẽ tìm hiểu khái niệm ngăn xếp quay lại và các chủ đề mới khác. Hẹn gặp bạn ở lớp học này nhé!
10. 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. Hãy sử dụng nhánh viewmodel để lấy hoặc tải mã.
Để lấy mã cho lớp học lập trình này và mở trong Android Studio, hãy thực hiện các bước sau.
Lấy mã
- Nhấp vào URL được cung cấp. Thao tác này sẽ mở trang GitHub của dự án trong một trình duyệt.
- Trên trang GitHub của dự án, hãy nhấp vào nút Code (Mã), một hộp thoại sẽ xuất hiện.
- Trong hộp thoại này, hãy 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.
- 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)).
- 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
- Khởi động Android Studio.
- 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 an existing Android Studio project (Mở một dự án hiện có trong Android Studio).
Lưu ý: Nếu Android Studio đã mở sẵn thì thay vào đó, hãy chọn tuỳ chọn sau đây trong trình đơn File > New > Import Project (Tệp > Mới > Nhập dự án).
- Trong hộp thoại Import Project (Nhập dự án), hãy chuyển đến nơi chứa thư mục dự án đã giải nén (thường nằm trong thư mục Downloads (Tệp đã tải xuống)).
- Nhấp đúp vào thư mục dự án đó.
- Chờ Android Studio mở dự án.
- Nhấp vào nút Run (Chạy) để xây dựng và chạy ứng dụng. Hãy đảm bảo ứng dụng được dựng như mong đợi.
- Duyệt qua các tệp dự án trong cửa sổ công cụ Project (Dự án) để xem cách ứng dụng được thiết lập.
11. Tóm tắt
ViewModel
là một phần của Bộ thành phần cấu trúc Android và dữ liệu ứng dụng lưu trongViewModel
được lưu giữ trong quá trình thay đổi cấu hình. Để thêmViewModel
vào ứng dụng, hãy tạo một lớp mới được mở rộng từ lớpViewModel
.ViewModel
dùng chung dùng để lưu dữ liệu ứng dụng giữa nhiều mảnh trong mộtViewModel
. Nhiều mảnh trong ứng dụng sẽ truy cậpViewModel
dùng chung này theo phạm vi hoạt động của các mảnh đó.LifecycleOwner
là một lớp có vòng đời trên Android, chẳng hạn như một hoạt động hoặc một mảnh.- Trình quan sát
LiveData
chỉ theo dõi các thay đổi đối với dữ liệu của ứng dụng khi chủ sở hữu vòng đời ở trạng thái đang hoạt động (STARTED
hoặcRESUMED
). - Liên kết trình nghe là các biểu thức lambda sẽ chạy khi xảy ra một sự kiện, chẳng hạn như sự kiện
onClick
. Các biểu thức này tương tự các tham chiếu phương thức nhưtextview.setOnClickListener(clickListener)
nhưng tính năng liên kết trình nghe cho phép bạn chạy các biểu thức liên kết dữ liệu tuỳ ý. - (Các) phương thức biến đổi
LiveData
đem đến một cách để thao tác dữ liệu trên nguồnLiveData
và trả về một đối tượngLiveData
. - Nền tảng Android cung cấp một lớp có tên
SimpleDateFormat
, dùng để định dạng và phân tích cú pháp ngày tháng theo miền bản địa. Lớp này cho phép định dạng (ngày → văn bản) và phân tích cú pháp ngày tháng (văn bản → ngày).