1. Trước khi bắt đầu
Lớp học lập trình này giới thiệu một ứng dụng mới có tên là Lunch Tray (Bữa ăn trưa) mà bạn sẽ tự mình xây dựng. Trong lớp học lập trình này, bạn sẽ được hướng dẫn từng bước hoàn thành dự án ứng dụng Lunch Tray, bao gồm cả việc thiết lập và kiểm thử dự án trong Android Studio.
Lớp học lập trình này khác với các lớp học khác trong khoá học này. Không giống các lớp học lập trình trước đây, lớp học lập trình này không nhằm mục đích hướng dẫn từng bước về cách xây dựng ứng dụng. Thay vào đó, lớp học hướng đến việc thiết lập một dự án mà bạn sẽ hoàn tất một cách độc lập, đồng thời đưa ra hướng dẫn về cách tự hoàn tất ứng dụng cũng như tự kiểm tra.
Thay vì đưa ra mã nguồn giải pháp, chúng tôi sẽ cung cấp một bộ công cụ kiểm thử trong ứng dụng mà bạn sẽ tải xuống. Bạn sẽ chạy các bài kiểm thử này trong Android Studio (chúng tôi sẽ hướng dẫn bạn cách thực hiện việc này sau trong lớp học lập trình) để xem mã nguồn của bạn có vượt qua được các bài kiểm thử hay không. Bạn có thể sẽ phải thử vài lần, kể cả các nhà phát triển chuyên nghiệp cũng hiếm khi vượt qua được ngay từ lượt kiểm thử đầu tiên! Sau khi mã nguồn của bạn vượt qua mọi bài kiểm thử, bạn có thể coi như dự án này đã hoàn tất.
Chúng tôi hiểu rằng có thể bạn muốn có mã nguồn giải pháp chỉ để kiểm tra lại. Chúng tôi cố ý không cung cấp mã nguồn giải pháp vì muốn bạn thực hành như một nhà phát triển chuyên nghiệp. Để làm được như vậy, có thể bạn sẽ phải vận dụng một số kỹ năng mà bạn chưa thực hành nhiều, chẳng hạn như:
- Dùng Google để tra cứu các thuật ngữ, thông báo lỗi và các đoạn mã trong ứng dụng mà bạn thấy lạ;
- Kiểm thử mã, đọc lỗi, sau đó điều chỉnh mã rồi kiểm thử lại;
- Quay lại nội dung trước đó trong Android Basics (Thông tin cơ bản về Android) để ôn lại kiến thức đã học;
- So sánh đoạn mã mà bạn biết là hoạt động tốt (chẳng hạn như đoạn mã được cung cấp trong dự án hoặc đoạn mã giải pháp trước đây trong các ứng dụng khác ở Bài 3) với đoạn mã mà bạn đang viết.
Việc này thoạt nhìn có vẻ sẽ rất khó khăn, nhưng chúng tôi chắc chắn 100% rằng nếu bạn có thể hoàn thành Bài 3 thì bạn đã sẵn sàng cho dự án này. Hãy bình tĩnh và đừng bỏ cuộc. Bạn có thể làm được!
Điều kiện tiên quyết
- Dự án này dành cho những người dùng đã hoàn thành Bài 3 của khoá học Android cơ bản: Kotlin.
Sản phẩm bạn sẽ tạo ra
- Bạn sẽ tải một ứng dụng đặt món ăn có tên Lunch Tray, triển khai ViewModel có liên kết dữ liệu và thêm tính năng điều hướng giữa các phân đoạn.
Bạn cần có
- Một máy tính đã cài đặt Android Studio.
2. Tổng quan về ứng dụng đã hoàn thiện
Chào mừng bạn đến với Dự án: Lunch Tray!
Như bạn đã biết, điều hướng là một phần cơ bản của quá trình phát triển cho Android. Cho dù đang sử dụng một ứng dụng để duyệt xem công thức nấu ăn, tìm đường đến nhà hàng yêu thích hay quan trọng nhất là đặt món ăn, chắc chắn bạn đều phải di chuyển giữa nhiều màn hình nội dung. Trong dự án này, bạn sẽ tận dụng các kỹ năng đã học ở Bài 3 để xây dựng ứng dụng đặt đồ ăn trưa có tên Lunch Tray, triển khai mô hình hiển thị, liên kết dữ liệu và di chuyển giữa các màn hình.
Dưới đây là ảnh chụp màn hình khi ứng dụng đã hoàn thiện. Khi mở ứng dụng Lunch Tray lần đầu, người dùng sẽ được chào đón bằng màn hình có duy nhất một nút với nội dung "Start Order" (Bắt đầu đặt hàng).
Sau khi nhấp vào Start Order, người dùng có thể chọn một món chính trong các lựa chọn có sẵn. Người dùng có thể thay đổi lựa chọn; thao tác thay đổi sẽ cập nhật Subtotal (Thành tiền) xuất hiện ở dưới cùng.
Màn hình tiếp theo cho phép người dùng thêm một món phụ.
Màn hình sau đó cho phép người dùng chọn một món ăn kèm (accompaniment) cho đơn gọi món của họ.
Cuối cùng, người dùng sẽ thấy một bản tóm tắt chi phí đơn gọi món gồm các phần như thành tiền (subtotal), thuế bán hàng (sales tax) và tổng chi phí (total cost). Người dùng cũng có thể gửi hoặc huỷ đơn.
Cả hai tuỳ chọn đều đưa người dùng trở về màn hình đầu tiên. Nếu người dùng đã gửi đơn gọi món, một thông báo ngắn sẽ xuất hiện ở cuối màn hình cho họ biết là đơn gọi món đã được gửi.
3. Bắt đầu
Tải mã dự án xuống
Lưu ý tên thư mục là android-basics-kotlin-lunch-tray-app
. Chọn thư mục này khi bạn mở dự án trong Android Studio.
- Chuyển đến trang kho lưu trữ GitHub được cung cấp cho dự án.
- 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.
- 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.
- 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.
- 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 (Mở).
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.
- 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)).
- 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) để tạo và chạy ứng dụng. Hãy đảm bảo ứng dụng được tạo như mong đợi.
Trước khi bắt đầu triển khai ViewModel
và hoạt động điều hướng, bạn hãy dành một chút thời gian để đảm bảo dự án được tạo thành công và làm quen với dự án đó. Trong lần đầu chạy ứng dụng, bạn sẽ thấy một màn hình trống. MainActivity
sẽ không trình bày phân đoạn nào vì bạn chưa thiết lập biểu đồ điều hướng.
Cấu trúc dự án sẽ tương tự như các dự án khác mà bạn đã xử lý. Có các gói riêng biệt dành cho dữ liệu, mô hình và giao diện người dùng, cũng như các thư mục riêng cho tài nguyên.
Tất cả các tuỳ chọn món mà người dùng có thể đặt (món chính, món phụ và món ăn kèm) đều được biểu thị bằng lớp MenuItem
trong gói model (mô hình). Các đối tượng MenuItem
có tên, thông tin mô tả, giá và loại.
data class MenuItem(
val name: String,
val description: String,
val price: Double,
val type: Int
) {
fun getFormattedPrice(): String = NumberFormat.getCurrencyInstance().format(price)
}
Loại được biểu thị bằng một số nguyên lấy từ đối tượng ItemType
trong gói constants (hằng số).
object ItemType {
val ENTREE = 1
val SIDE_DISH = 2
val ACCOMPANIMENT = 3
}
Bạn có thể tìm thấy từng đối tượng MenuItem
trong DataSource.kt
trong gói dữ liệu.
object DataSource {
val menuItems = mapOf(
"cauliflower" to
MenuItem(
name = "Cauliflower",
description = "Whole cauliflower, brined, roasted, and deep fried",
price = 7.00,
type = ItemType.ENTREE
),
...
}
Đối tượng này chỉ chứa một mô hình ánh xạ bao gồm một khoá và một MenuItem
tương ứng. Trước tiên, bạn sẽ truy cập vào DataSource
trong ObjectViewModel
(phần bạn sẽ triển khai đầu tiên)
Xác định ViewModel
Như đã thấy trong ảnh chụp màn hình ở trang trước, ứng dụng cần người dùng cung cấp 3 mục: 1 món chính, 1 món phụ và 1 món ăn kèm. Sau đó, màn hình tóm tắt đơn gọi món cho biết thành tiền và tính thuế bán hàng dựa trên các món đã chọn (thuế này dùng để tính tổng chi phí).
Trong gói model, hãy mở OrderViewModel.kt
và bạn sẽ thấy một vài biến đã được xác định. Thuộc tính menuItems
cho phép bạn truy cập vào DataSource
qua ViewModel
.
val menuItems = DataSource.menuItems
Trước tiên, cũng có một số biến cho previousEntreePrice
, previousSidePrice
và previousAccompanimentPrice
. Vì giá trị "thành tiền" được cập nhật khi người dùng đưa ra lựa chọn (thay vì được thêm vào ở bước cuối), nên các biến này được dùng để theo dõi lựa chọn trước của người dùng nếu họ thay đổi lựa chọn trước khi chuyển sang màn hình tiếp theo. Bạn sẽ sử dụng các biến này để đảm bảo "thành tiền" tính đến phần chênh lệch giữa giá của mặt hàng trước và mặt hàng đang chọn.
private var previousEntreePrice = 0.0
private var previousSidePrice = 0.0
private var previousAccompanimentPrice = 0.0
Ngoài ra, còn có các biến private như _entree
, _side
và _accompaniment
để lưu trữ lựa chọn hiện tại. Các biến này thuộc kiểu MutableLiveData<MenuItem?>
. Mỗi biến đi kèm một thuộc tính sao lưu công khai, entree
, side
và accompaniment
(thuộc kiểu dữ liệu không thể thay đổi LiveData<MenuItem?>
). Bố cục của phân đoạn sẽ truy cập các biến này để hiển thị các mặt hàng đã chọn lên màn hình. MenuItem
bên trong đối tượng LiveData
cũng có thể ở trạng thái rỗng vì người dùng có thể không chọn một món chính, món phụ và/hoặc món ăn kèm.
// Entree for the order
private val _entree = MutableLiveData<MenuItem?>()
val entree: LiveData<MenuItem?> = _entree
// Side for the order
private val _side = MutableLiveData<MenuItem?>()
val side: LiveData<MenuItem?> = _side
// Accompaniment for the order.
private val _accompaniment = MutableLiveData<MenuItem?>()
val accompaniment: LiveData<MenuItem?> = _accompaniment
Ngoài ra còn có các biến LiveData
cho phần thành tiền, tổng và thuế. Các biến này dùng định dạng số để có thể xuất hiện dưới dạng tiền tệ.
// Subtotal for the order
private val _subtotal = MutableLiveData(0.0)
val subtotal: LiveData<String> = Transformations.map(_subtotal) {
NumberFormat.getCurrencyInstance().format(it)
}
// Total cost of the order
private val _total = MutableLiveData(0.0)
val total: LiveData<String> = Transformations.map(_total) {
NumberFormat.getCurrencyInstance().format(it)
}
// Tax for the order
private val _tax = MutableLiveData(0.0)
val tax: LiveData<String> = Transformations.map(_tax) {
NumberFormat.getCurrencyInstance().format(it)
}
Cuối cùng, giá trị của thuế suất được cố định trong mã là 0,08 (8%).
private val taxRate = 0.08
Có 6 phương thức trong OrderViewModel
mà bạn sẽ cần triển khai.
setEntree(), setSide() và setAccompaniment()
Tất cả các phương thức này phải hoạt động như nhau cho món chính, món phụ và món ăn kèm. Ví dụ: setEntree()
phải hoạt động như sau:
- Nếu
_entree
không có giá trịnull
(tức là người dùng đã chọn một món chính nhưng đã thay đổi lựa chọn), hãy đặtpreviousEntreePrice
thành giá củacurrent _entree
. - Nếu
_subtotal
không phải lànull
, hãy trừpreviousEntreePrice
khỏi tổng giá trị thành tiền. - Cập nhật giá trị của
_entree
thành món chính đã truyền vào hàm (truy cập vàoMenuItem
bằngmenuItems
). - Gọi
updateSubtotal()
, truyền vào giá của món chính mới chọn.
Logic của setSide()
và setAccompaniment()
hoàn toàn giống với setEntree()
.
updateSubtotal()
updateSubtotal()
được gọi với một đối số cho giá mới. Giá này sẽ được thêm vào thành tiền. Phương thức này sẽ thực hiện 3 việc sau:
- Nếu
_subtotal
không phải lànull
, thêmitemPrice
vào_subtotal
. - Nếu
_subtotal
lànull
, đặt_subtotal
thànhitemPrice
. - Sau khi đặt (hoặc cập nhật)
_subtotal
, gọicalculateTaxAndTotal()
để các giá trị này được cập nhật nhằm phản ánh giá trị thành tiền mới.
calculateTaxAndTotal()
calculateTaxAndTotal()
phải cập nhật các biến cho thuế và tổng chi phí dựa trên giá trị thành tiền. Triển khai phương thức như sau:
- Đặt
_tax
bằng thuế suất nhân với thành tiền. - Đặt
_total
bằng thành tiền cộng với thuế.
resetOrder()
resetOrder()
sẽ được gọi khi người dùng gửi hoặc huỷ đơn gọi món. Bạn cần đảm bảo ứng dụng không còn dữ liệu khi người dùng bắt đầu một đơn gọi món mới.
Triển khai resetOrder()
bằng cách đặt tất cả các biến mà bạn đã sửa đổi trong OrderViewModel
về giá trị ban đầu (0.0 hoặc null).
Tạo biến liên kết dữ liệu
Triển khai chức năng liên kết dữ liệu trong các tệp bố cục. Mở tệp bố cục và thêm biến liên kết dữ liệu thuộc kiểu OrderViewModel
và/hoặc lớp của phân đoạn tương ứng.
Bạn sẽ cần triển khai tất cả các nhận xét TODO
để thiết lập văn bản và trình nghe sự kiện nhấp trong 4 tệp bố cục:
fragment_entree_menu.xml
fragment_side_menu.xml
fragment_accompaniment_menu.xml
fragment_checkout.xml
Mỗi nhiệm vụ cụ thể được ghi chú trong một nhận xét TODO trong tệp bố cục. Các bước được tóm tắt như sau:
- Trong
fragment_entree_menu.xml
, trong thẻ<data>
, hãy thêm một biến liên kết choEntreeMenuFragment
. Đối với mỗi nút chọn, bạn cần đặt món chính vàoViewModel
khi người dùng chọn món đó. Văn bản trong khung hiển thị văn bản của "thành tiền" phải được cập nhật tương ứng. Bạn cũng cần đặt thuộc tínhonClick
chocancel_button
vànext_button
để huỷ đơn gọi món hoặc chuyển đến màn hình tiếp theo. - Làm tương tự trong
fragment_side_menu.xml
, hãy thêm một biến liên kết choSideMenuFragment
, ngoại trừ việc đặt món phụ vào khung hiển thị khi người dùng nhấn nút chọn. Văn bản trong phần thành tiền cũng cần được cập nhật và bạn cũng cần đặt thuộc tínhonClick
cho nút huỷ và nút "tiếp theo". - Làm tương tự một lần nữa nhưng trong
fragment_accompaniment_menu.xml
, lần này với một biến liên kết choAccompanimentMenuFragment
và thiết lập món ăn kèm (accompaniment) khi mỗi nút chọn được chọn. Xin nhắc lại, bạn cũng cần đặt các thuộc tính cho văn bản thành tiền (subtotal), nút huỷ (cancel) và nút tiếp theo (next). - Trong
fragment_checkout.xml
, bạn cần thêm một thẻ<data>
để có thể xác định các biến liên kết. Trong thẻ<data>
, hãy thêm 2 biến liên kết, một biến choOrderViewModel
và một biến choCheckoutFragment
. Trong khung hiển thị văn bản, bạn cần đặt tên và giá của món chính, món phụ và món ăn kèm trongOrderViewModel
. Bạn cũng cần đặt các biến thành tiền, thuế và tổng trongOrderViewModel
. Sau đó, hãy đặtonClickAttributes
để xác định thời điểm gửi đơn gọi món và thời điểm huỷ đơn gọi món bằng các hàm phù hợp trongCheckoutFragment
.
.
Khởi động các biến liên kết dữ liệu trong phân đoạn
Hãy khởi động các biến liên kết dữ liệu trong các tệp phân đoạn tương ứng bên trong phương thức, onViewCreated()
.
EntreeMenuFragment
SideMenuFragment
AccompanimentMenuFragment
CheckoutFragment
Tạo biểu đồ điều hướng
Như bạn đã tìm hiểu trong Bài 3, một biểu đồ điều hướng được lưu trữ trong FragmentContainerView
và nằm trong một hoạt động. Mở activity_main.xml
rồi thay thế TODO bằng mã sau để khai báo FragmentContainerView
.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Bạn có thể tìm thấy biểu đồ điều hướng mobile_navigation.xml
trong gói res.navigation.
Đây là biểu đồ điều hướng cho ứng dụng này. Tuy nhiên, tệp này đang trống. Nhiệm vụ của bạn là thêm đích đến vào biểu đồ điều hướng và mô hình hoá hoạt động điều hướng sau đây giữa các màn hình.
- Điều hướng từ
StartOrderFragment
đếnEntreeMenuFragment
- Điều hướng từ
EntreeMenuFragment
đếnSideMenuFragment
- Điều hướng từ
SideMenuFragment
đếnAccompanimentMenuFragment
- Điều hướng từ
AccompanimentMenuFragment
đếnCheckoutFragment
- Điều hướng từ
CheckoutFragment
đếnStartOrderFragment
- Điều hướng từ
EntreeMenuFragment
đếnStartOrderFragment
- Điều hướng từ
SideMenuFragment
đếnStartOrderFragment
- Điều hướng từ
AccompanimentMenuFragment
đếnStartOrderFragment
- Điểm bắt đầu (Start Destination) phải là
StartOrderFragment
Sau khi đã thiết lập biểu đồ điều hướng, bạn sẽ cần triển khai hoạt động điều hướng trong các lớp phân đoạn. Triển khai các nhận xét TODO
còn lại trong các phân đoạn cũng như trong MainActivity.kt
.
- Đối với phương thức
goToNextScreen()
trongEntreeMenuFragment
,SideMenuFragment
vàAccompanimentMenuFragment
, hãy điều hướng đến màn hình tiếp theo trong ứng dụng. - Đối với phương thức
cancelOrder()
trongEntreeMenuFragment
,SideMenuFragment
,AccompanimentMenuFragment
vàCheckoutFragment
, đầu tiên hãy gọiresetOrder()
trênsharedViewModel
, sau đó điều hướng đếnStartOrderFragment
. - Trong
StartOrderFragment
, hãy triển khaisetOnClickListener()
để điều hướng đếnEntreeMenuFragment
. - Trong
CheckoutFragment
, hãy triển khai phương thứcsubmitOrder()
. GọiresetOrder()
trênsharedViewModel
, sau đó điều hướng đếnStartOrderFragment
. - Cuối cùng, trong
MainActivity.kt
, hãy đặtnavController
thànhnavController
trongNavHostFragment
.
4. Kiểm thử ứng dụng
Dự án Lunch Tray chứa mục tiêu "androidTest" với một số trường hợp kiểm thử: MenuContentTests
, NavigationTests
và OrderFunctionalityTests
.
Chạy bài kiểm thử
Để chạy kiểm thử, bạn có thể làm theo một trong những cách sau:
Đối với trường hợp kiểm thử đơn lẻ, hãy mở một lớp trường hợp kiểm thử (test case) rồi nhấp vào mũi tên màu xanh lục ở bên trái phần khai báo về lớp đó. Sau đó, chọn tuỳ chọn Run (Chạy) trên trình đơn. Thao tác này sẽ chạy tất cả các hoạt động kiểm thử trong trường hợp kiểm thử đó.
Thông thường, bạn sẽ chỉ muốn chạy một chương trình kiểm thử, chẳng hạn như khi một kiểm thử không đạt còn các kiểm thử khác thì đạt. Bạn có thể chạy một kiểm thử duy nhất tương tự như cách thực hiện trên toàn bộ trường hợp kiểm thử. Sử dụng mũi tên màu xanh lục rồi chọn tuỳ chọn Run (Chạy).
Nếu có nhiều trường hợp kiểm thử, bạn cũng có thể chạy toàn bộ bộ kiểm thử. Tương tự như cách chạy ứng dụng, bạn có thể thấy tuỳ chọn này trên trình đơn Run (Chạy).
Vui lòng lưu ý Android Studio sẽ mặc định chuyển đến mục tiêu cuối cùng mà bạn đã chạy (ứng dụng, mục tiêu kiểm thử, v.v.). Do đó, nếu trình đơn vẫn hiển thị Run > Run 'app' (Chạy > Chạy "ứng dụng"), thì bạn có thể chạy mục tiêu kiểm thử này bằng cách chọn Run > Run (Chạy > Chạy).
Sau đó chọn mục tiêu kiểm thử trong trình đơn bật lên.
5. Không bắt buộc: Hãy cho chúng tôi biết ý kiến phản hồi của bạn!
Chúng tôi rất mong nhận được ý kiến của bạn về dự án này. Trả lời khảo sát ngắn này để góp ý cho chúng tôi. Ý kiến phản hồi của bạn sẽ giúp chúng tôi định hình các dự án trong tương lai.