1. Trước khi bắt đầu
Cho đến thời điểm này, các ứng dụng bạn đã xử lý đều chỉ có một màn hình. Tuy vậy, rất nhiều ứng dụng có thể có nhiều màn hình để bạn điều hướng. Ví dụ: ứng dụng Cài đặt có nhiều trang nội dung trên nhiều màn hình.
Trong quá trình phát triển Android hiện đại, các ứng dụng nhiều màn hình được tạo bằng thành phần Điều hướng Jetpack. Thành phần Điều hướng (Navigation) trong Compose cho phép bạn dễ dàng xây dựng ứng dụng nhiều màn hình trong Compose bằng phương pháp khai báo, tương tự như việc tạo giao diện người dùng. Lớp học lập trình này giới thiệu thông tin cơ bản của thành phần Điều hướng trong Compose, cách giúp AppBar (Thanh ứng dụng) phản hồi nhanh với thao tác điều hướng và cách gửi dữ liệu từ ứng dụng của bạn đến một ứng dụng khác bằng ý định, đồng thời thể hiện các phương pháp hay nhất trong một ứng dụng ngày càng phức tạp.
Điều kiện tiên quyết
- Làm quen với ngôn ngữ Kotlin gồm loại hàm, lambda và hàm phạm vi
- Làm quen với các bố cục
Row
vàColumn
cơ bản trong Compose
Kiến thức bạn sẽ học được
- Tạo một thành phần kết hợp
NavHost
để xác định các tuyến và màn hình trong ứng dụng. - Di chuyển giữa các màn hình bằng
NavHostController
. - Thao tác với ngăn xếp lui để chuyển về các màn hình trước.
- Dùng ý định để chia sẻ dữ liệu với một ứng dụng khác.
- Tuỳ chỉnh AppBar, bao gồm tiêu đề và nút quay lại.
Sản phẩm bạn sẽ tạo ra
- Bạn sẽ triển khai tính năng điều hướng trong một ứng dụng nhiều màn hình.
Bạn cần có
- Phiên bản mới nhất của Android Studio
- Kết nối Internet để tải mã khởi đầu xuống
2. Tải mã khởi đầu xuống
Để bắt đầu, hãy tải mã khởi đầu xuống:
Ngoài ra, bạn có thể sao chép kho lưu trữ GitHub cho mã:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-cupcake.git $ cd basic-android-kotlin-compose-training-cupcake $ git checkout starter
Nếu bạn muốn xem mã khởi đầu cho lớp học lập trình này, hãy xem trên GitHub.
3. Hướng dẫn từng bước về ứng dụng
Ứng dụng Cupcake hơi khác so với các ứng dụng bạn đã từng sử dụng. Thay vì toàn bộ nội dung xuất hiện trên một màn hình, ứng dụng sẽ có bốn màn hình riêng biệt và người dùng có thể di chuyển qua từng màn hình khi đang đặt bánh nướng. Nếu chạy ứng dụng này, bạn sẽ không nhìn thấy gì và bạn cũng không được điều hướng giữa các màn hình này vì thành phần điều hướng chưa được thêm vào đoạn mã của ứng dụng. Tuy nhiên, bạn vẫn có thể kiểm tra bản xem trước của mỗi màn hình rồi khớp các màn hình này với màn hình của phiên bản ứng dụng hoàn chỉnh dưới đây.
Màn hình bắt đầu đơn đặt hàng
Màn hình đầu tiên cho người dùng thấy ba nút tương ứng với số lượng bánh nướng để đặt hàng.
Trong đoạn mã, thành phần này được biểu thị bằng thành phần kết hợp StartOrderScreen
trong StartOrderScreen.kt
.
Màn hình là một cột duy nhất, có hình ảnh và văn bản, cùng với ba nút tuỳ chỉnh để đặt số lượng bánh nướng nhỏ. Các nút tuỳ chỉnh được triển khai bằng thành phần kết hợp SelectQuantityButton
, cũng nằm trong StartOrderScreen.kt
.
Chọn màn hình hương vị
Sau khi chọn số lượng, ứng dụng sẽ nhắc người dùng chọn hương vị cho bánh nướng. Ứng dụng dùng nút chọn để cho thấy nhiều lựa chọn. Người dùng có thể chọn một trong số các hương vị có thể chọn.
Danh sách hương vị được lưu trữ dưới dạng danh sách mã nhận dạng tài nguyên chuỗi trong data.DataSource.kt
.
Chọn màn hình ngày đến lấy hàng
Sau khi chọn một hương vị, ứng dụng sẽ hiện một loạt các nút chọn khác để người dùng chọn ngày đến lấy hàng. Các lựa chọn đến lấy hàng của danh sách được hàm pickupOptions()
trả về trong OrderViewModel
.
Cả màn hình Choose Flavor (Chọn hương vị) và màn hình Choose Pickup Date (Chọn ngày lấy hàng) đều được biểu thị bằng cùng một thành phần kết hợp SelectOptionScreen
trong SelectOptionScreen.kt
. Tại sao bạn nên sử dụng cùng một thành phần kết hợp? Bố cục của các màn hình này giống hệt nhau! Điểm khác biệt duy nhất giữa các màn hình là dữ liệu, nhưng bạn có thể sử dụng cùng một thành phần kết hợp để cho thấy cả màn hình hương vị lẫn màn hình chọn ngày lấy hàng.
Màn hình Tóm tắt đơn đặt hàng
Sau khi chọn ngày đến lấy hàng, ứng dụng sẽ hiện màn hình Order Summary (Tóm tắt đơn đặt hàng) để người dùng có thể xem lại rồi hoàn tất đơn đặt hàng.
Màn hình này được thành phần kết hợp OrderSummaryScreen
triển khai trong SummaryScreen.kt
.
Bố cục bao gồm một Column
chứa toàn bộ thông tin về đơn đặt hàng, một thành phần kết hợp Text
để tạo tổng giá tiền và các nút dùng để gửi đơn đặt hàng đến một ứng dụng khác hoặc huỷ đơn đặt hàng rồi quay lại màn hình đầu tiên.
Nếu người dùng chọn gửi đơn đặt hàng đến một ứng dụng khác, thì ứng dụng Cupcake sẽ hiển thị một Trang chia sẻ nội dung trong Android cho thấy các lựa chọn chia sẻ.
Trạng thái hiện tại của ứng dụng được lưu trữ trong data.OrderUiState.kt
. Lớp dữ liệu OrderUiState
chứa thuộc tính để lưu trữ lựa chọn của người dùng từ mỗi màn hình.
Các màn hình của ứng dụng sẽ được trình bày trong thành phần kết hợp CupcakeApp
. Tuy nhiên, trong dự án khởi động, ứng dụng chỉ cho thấy màn hình đầu tiên. Bạn hiện không thể điều hướng qua tất cả các màn hình của ứng dụng, nhưng đừng lo lắng, bạn ở đây để làm điều đó! Bạn sẽ tìm hiểu cách xác định tuyến điều hướng, thiết lập thành phần kết hợp NavHost để điều hướng giữa các màn hình (còn gọi là đích đến), thực hiện ý định tích hợp bằng thành phần giao diện người dùng hệ thống như màn hình chia sẻ, đồng thời giúp AppBar phản hồi các thay đổi về thao tác điều hướng.
Các thành phần kết hợp có thể sử dụng lại
Ứng dụng mẫu trong khoá học này được thiết kế để triển khai các phương pháp hay nhất khi thích hợp. Ứng dụng Cupcake cũng không ngoại lệ. Trong gói ui.components, bạn sẽ thấy một tệp có tên CommonUi.kt
chứa thành phần kết hợp FormattedPriceLabel
. Nhiều màn hình trong ứng dụng dùng thành phần kết hợp này để định dạng giá của đơn đặt hàng một cách nhất quán. Thay vì sao chép thành phần kết hợp Text
với cùng định dạng và đối tượng sửa đổi, bạn có thể xác định FormattedPriceLabel
một lần sau đó sử dụng lại cho các màn hình khác khi cần.
Màn hình hương vị và màn hình ngày đến lấy hàng sử dụng thành phần kết hợp SelectOptionScreen
. Bạn cũng có thể sử dụng lại thành phần này. Thành phần kết hợp này lấy một tham số có tên là options
thuộc loại List<String>
đại diện cho các lựa chọn sẽ hiển thị. Những lựa chọn này xuất hiện trong Row
, bao gồm một thành phần kết hợp RadioButton
và một thành phần kết hợp Text
chứa từng chuỗi. Column
bao quanh toàn bộ bố cục, đồng thời chứa một thành phần kết hợp Text
để hiện giá đã định dạng, nút Cancel (Huỷ) và nút Next (Tiếp theo).
4. Xác định các tuyến và tạo NavHostController
Các phần của Thành phần điều hướng
Thành phần điều hướng có ba phần chính:
- NavController: Chịu trách nhiệm điều hướng giữa các đích đến, tức là màn hình trong ứng dụng.
- NavGraph: Bản đồ các đích đến có thể kết hợp để điều hướng đến.
- NavHost: Thành phần kết hợp đóng vai trò như một vùng chứa để hiện đích đến hiện tại của NavGraph.
Trong lớp học lập trình này, bạn sẽ tập trung vào NavController và NavHost. Trong NavHost, bạn sẽ xác định các đích đến cho NavGraph của ứng dụng Cupcake.
Xác định tuyến cho các đích đến trong ứng dụng của bạn
Một trong những khái niệm cơ bản về tính năng điều hướng trong Compose là tuyến. Tuyến là một chuỗi tương ứng với một đích đến. Ý tưởng này tương tự như khái niệm về URL. Giống như một URL ánh xạ đến một trang khác trên trang web, tuyến là một chuỗi ánh xạ tới một đích đến và đóng vai trò là giá trị nhận dạng riêng biệt của đích đến đó. Thông thường, đích đến là một thành phần kết hợp hoặc nhóm các thành phần kết hợp tương ứng với những gì người dùng nhìn thấy. Ứng dụng Cupcake cần các đích đến cho màn hình bắt đầu đặt hàng, màn hình hương vị, màn hình ngày lấy hàng và màn hình tóm tắt đơn đặt hàng.
Ứng dụng có số lượng màn hình giới hạn, theo đó cũng giới hạn về số lượng các tuyến. Bạn có thể xác định các tuyến của một ứng dụng bằng lớp enum. Các lớp enum trong Kotlin có thuộc tính tên trả về một chuỗi có tên thuộc tính.
Bạn sẽ bắt đầu bằng cách xác định 4 tuyến cho ứng dụng Cupcake.
Start
: Dùng một trong ba nút để chọn số lượng bánh nướng.Flavor
: Chọn hương vị trong danh sách các lựa chọn.Pickup
: Chọn ngày lấy hàng trong danh sách các lựa chọn.Summary
: Xem lại các lựa chọn rồi gửi hoặc huỷ đơn đặt hàng.
Thêm một lớp enum để xác định các tuyến.
- Trong
CupcakeScreen.kt
, phía trên thành phần kết hợpCupcakeAppBar
, hãy thêm một lớp enum có tênCupcakeScreen
.
enum class CupcakeScreen() {
}
- Thêm 4 trường hợp vào lớp enum:
Start
,Flavor
,Pickup
vàSummary
.
enum class CupcakeScreen() {
Start,
Flavor,
Pickup,
Summary
}
Thêm NavHost vào ứng dụng
NavHost là một thành phần kết hợp cho thấy các đích đến có thể kết hợp khác, dựa trên một tuyến nhất định. Ví dụ: nếu tuyến là Flavor
, thì NavHost
sẽ hiển thị màn hình để bạn chọn hương vị bánh nướng. Nếu tuyến là Summary
thì ứng dụng sẽ cho thấy màn hình tóm tắt.
Cú pháp cho NavHost
cũng giống như mọi Thành phần kết hợp khác.
Có hai tham số đáng chú ý.
navController
: Một bản sao của lớpNavHostController
. Bạn có thể sử dụng đối tượng này để điều hướng giữa các màn hình, chẳng hạn như bằng cách gọi phương thứcnavigate()
để điều hướng đến một đích đến khác. Bạn có thể lấyNavHostController
bằng cách gọirememberNavController()
từ một hàm có khả năng kết hợp.startDestination
: Một tuyến của chuỗi xác định đích đến sẽ xuất hiện theo mặc định khi ứng dụng hiệnNavHost
lần đầu tiên. Trong trường hợp ứng dụng Cupcake, đây phải là tuyếnStart
.
Giống như các thành phần kết hợp khác, NavHost
cũng lấy tham số modifier
.
Bạn sẽ thêm NavHost
vào thành phần kết hợp CupcakeApp
trong CupcakeScreen.kt
. Trước tiên, bạn cần tham chiếu đến trình điều khiển điều hướng. Bạn có thể sử dụng trình điều khiển điều hướng trong cả NavHost
bạn đang thêm và AppBar
mà bạn sẽ thêm ở bước sau. Do đó, bạn nên khai báo biến trong thành phần kết hợp CupcakeApp()
.
- Mở
CupcakeScreen.kt
. - Trong
Scaffold
, bên dưới biếnuiState
, hãy thêm một thành phần kết hợpNavHost
.
import androidx.navigation.compose.NavHost
Scaffold(
...
) { innerPadding ->
val uiState by viewModel.uiState.collectAsState()
NavHost()
}
- Truyền biến
navController
cho tham sốnavController
vàCupcakeScreen.Start.name
cho tham sốstartDestination
. Truyền đối tượng sửa đổi đã truyền vàoCupcakeApp()
cho tham số của đối tượng sửa đổi. Truyền trailing lambda (lambda theo sau) trống cho thông số cuối cùng.
import androidx.compose.foundation.layout.padding
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
}
Xử lý các tuyến trong NavHost
Giống như những thành phần kết hợp khác, NavHost
nhận một loại hàm cho nội dung.
Trong hàm nội dung của NavHost
, bạn hãy gọi hàm composable()
. Hàm composable()
có hai tham số bắt buộc.
route
: Một chuỗi tương ứng với tên của một tuyến. Đây có thể là một chuỗi duy nhất. Bạn sẽ sử dụng thuộc tính tên của các hằng số enumCupcakeScreen
.content
: Tại đây, bạn có thể gọi thành phần kết hợp mà bạn muốn trình bày cho tuyến vừa nêu.
Bạn sẽ gọi hàm composable()
một lần cho mỗi tuyến.
- Gọi hàm
composable()
, truyềnCupcakeScreen.Start.name
choroute
.
import androidx.navigation.compose.composable
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
}
}
- Trong trailing lambda (lambda theo sau), gọi thành phần kết hợp
StartOrderScreen
để truyềnquantityOptions
cho thuộc tínhquantityOptions
. Đối vớimodifier
, hãy truyềnModifier.fillMaxSize().padding(dimensionResource(R.dimen.padding_medium))
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.res.dimensionResource
import com.example.cupcake.ui.StartOrderScreen
import com.example.cupcake.data.DataSource
NavHost(
navController = navController,
startDestination = CupcakeScreen.Start.name,
modifier = Modifier.padding(innerPadding)
) {
composable(route = CupcakeScreen.Start.name) {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}
- Bên dưới lệnh gọi đầu tiên đến
composable()
, hãy gọi lạicomposable()
, truyềnCupcakeScreen.Flavor.name
choroute
.
composable(route = CupcakeScreen.Flavor.name) {
}
- Trong trailing lambda, hãy tham chiếu đến
LocalContext.current
rồi lưu trữ nó trong một biến có tên làcontext
.Context
là một lớp trừu tượng trong đó việc triển khai được hệ thống Android cung cấp. Lớp này cho phép bạn truy cập vào những tài nguyên và lớp cụ thể dành cho ứng dụng, cũng như các lệnh gọi lên dành cho hoạt động ở cấp ứng dụng, chẳng hạn như khởi chạy các hoạt động, v.v. Bạn có thể dùng biến này để lấy các chuỗi của danh sách mã nhận dạng tài nguyên trong mô hình khung hiển thị để cho thấy danh sách hương vị.
import androidx.compose.ui.platform.LocalContext
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
}
- Gọi thành phần kết hợp
SelectOptionScreen
.
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
)
}
- Màn hình hương vị cần hiển thị và cập nhật tổng giá tiền khi người dùng chọn hương vị. Truyền vào
uiState.price
cho tham sốsubtotal
.
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price
)
}
- Màn hình hương vị lấy danh sách hương vị qua tài nguyên chuỗi của ứng dụng. Chuyển đổi danh sách mã nhận dạng tài nguyên thành danh sách chuỗi bằng cách sử dụng hàm
map()
rồi gọicontext.resources.getString(id)
cho mỗi hương vị.
import com.example.cupcake.ui.SelectOptionScreen
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
options = DataSource.flavors.map { id -> context.resources.getString(id) }
)
}
- Đối với tham số
onSelectionChanged
, hãy truyền biểu thức lambda gọisetFlavor()
trên mô hình khung hiển thị, truyềnit
(đối số đã truyền vàoonSelectionChanged()
). Đối với tham sốmodifier
, hãy truyềnModifier.fillMaxHeight().
import androidx.compose.foundation.layout.fillMaxHeight
import com.example.cupcake.data.DataSource.flavors
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
}
Màn hình ngày lấy hàng cũng tương tự như màn hình hương vị. Điểm khác biệt duy nhất là dữ liệu được truyền vào thành phần kết hợp SelectOptionScreen
.
- Gọi lại hàm
composable()
, truyềnCupcakeScreen.Pickup.name
cho tham sốroute
.
composable(route = CupcakeScreen.Pickup.name) {
}
- Trong trailing lambda, hãy gọi thành phần kết hợp
SelectOptionScreen
và truyềnuiState.price
vàosubtotal
như trước. TruyềnuiState.pickupOptions
cho tham sốoptions
và biểu thức lambda gọisetDate()
trênviewModel
cho tham sốonSelectionChanged
. Đối với tham sốmodifier
, hãy truyềnModifier.fillMaxHeight().
SelectOptionScreen(
subtotal = uiState.price,
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) },
modifier = Modifier.fillMaxHeight()
)
- Gọi
composable()
lại một lần nữa, truyềnCupcakeScreen.Summary.name
choroute
.
composable(route = CupcakeScreen.Summary.name) {
}
- Trong trailing lambda, hãy gọi thành phần kết hợp
OrderSummaryScreen()
, truyền biếnuiState
cho tham sốorderUiState
. Đối với tham sốmodifier
, hãy truyềnModifier.fillMaxHeight().
import com.example.cupcake.ui.OrderSummaryScreen
composable(route = CupcakeScreen.Summary.name) {
OrderSummaryScreen(
orderUiState = uiState,
modifier = Modifier.fillMaxHeight()
)
}
Đó là các bước để thiết lập NavHost
. Ở phần tiếp theo, bạn sẽ làm cho ứng dụng thay đổi các tuyến và di chuyển giữa các màn hình khi người dùng nhấn vào từng nút.
5. Di chuyển giữa các tuyến
Bây giờ, khi bạn đã xác định và ánh xạ các tuyến tới thành phần kết hợp trong NavHost
, đã đến lúc điều hướng giữa các màn hình. Thuộc tính NavHostController
(thuộc tính navController
từ việc gọi rememberNavController()
) chịu trách nhiệm di chuyển giữa các tuyến. Tuy nhiên, hãy lưu ý rằng, thuộc tính này được xác định trong thành phần kết hợp CupcakeApp
. Bạn cần có cách để truy cập ứng dụng từ các màn hình khác nhau trong ứng dụng của mình.
Thật dễ dàng đúng không? Bạn chỉ cần truyền navController
dưới dạng tham số cho từng thành phần kết hợp.
Mặc dù có thể dùng phương pháp này, nhưng đây không phải là cách lý tưởng để cấu trúc ứng dụng. Do đó, một trong những lợi ích của việc sử dụng NavHost để xử lý hoạt động điều hướng là logic điều hướng được tách khỏi giao diện người dùng. Tuỳ chọn này tránh được một số hạn chế lớn khi truyền navController
dưới dạng tham số.
- Logic điều hướng được lưu giữ tại cùng địa điểm, giúp mã dễ bảo trì hơn và ngăn lỗi bằng cách không vô tình cung cấp cho các màn hình quyền tự do điều hướng trong ứng dụng.
- Đối với ứng dụng cần hoạt động trên nhiều kiểu dáng (như điện thoại ở chế độ dọc, điện thoại có thể gập lại hoặc máy tính bảng màn hình lớn), một nút có thể hoặc không thể kích hoạt tính năng điều hướng, tuỳ thuộc vào bố cục ứng dụng. Mỗi màn hình riêng lẻ phải độc lập và không cần nhận biết màn hình khác trong ứng dụng.
Thay vào đó, cách tiếp cận của chúng ta là truyền một loại hàm vào từng thành phần kết hợp cho những gì sẽ xảy ra khi người dùng nhấp vào nút. Theo đó, thành phần kết hợp và bất kỳ thành phần kết hợp con nào của nó sẽ quyết định thời điểm gọi hàm. Tuy nhiên, logic điều hướng không thể hiện trên mỗi màn hình trong ứng dụng. Tất cả hành vi điều hướng đều được xử lý trong NavHost.
Thêm trình xử lý nút vào StartOrderScreen
Bạn sẽ bắt đầu bằng cách thêm một tham số loại hàm được gọi khi người dùng nhấn một trong các nút số lượng ở màn hình đầu tiên. Hàm này được truyền vào thành phần kết hợp StartOrderScreen
, chịu trách nhiệm cập nhật viewmodel và chuyển đến màn hình tiếp theo.
- Mở
StartOrderScreen.kt
. - Bên dưới tham số
quantityOptions
và trước tham số của đối tượng sửa đổi, hãy thêm tham số có tên làonNextButtonClicked
thuộc loại() -> Unit
.
@Composable
fun StartOrderScreen(
quantityOptions: List<Pair<Int, Int>>,
onNextButtonClicked: () -> Unit,
modifier: Modifier = Modifier
){
...
}
- Giờ đây, khi thành phần kết hợp
StartOrderScreen
chờ đợi một giá trị dành choonNextButtonClicked
, hãy tìmStartOrderPreview
rồi truyền một nội dung hàm lambda trống cho tham sốonNextButtonClicked
.
@Preview
@Composable
fun StartOrderPreview() {
CupcakeTheme {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
onNextButtonClicked = {},
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
}
Mỗi nút tương ứng với một số lượng bánh nướng riêng biệt. Bạn sẽ cần thông tin này để hàm được truyền vào onNextButtonClicked
có thể cập nhật viewmodel cho phù hợp.
- Sửa đổi loại của tham số
onNextButtonClicked
để lấy tham sốInt
.
onNextButtonClicked: (Int) -> Unit,
Để Int
truyền vào khi gọi onNextButtonClicked()
, hãy xem loại tham số quantityOptions
.
Loại này là List<Pair<Int, Int>>
hoặc danh sách Pair<Int, Int>
. Kiểu Pair
nghe có thể xa lạ, nhưng chỉ đơn giản là một cặp giá trị giống như tên của kiểu này. Pair
nhận hai tham số kiểu dữ liệu chung. Trong trường hợp này, cả hai đều thuộc kiểu Int
.
Mỗi mục trong một cặp được thuộc tính đầu tiên hoặc thuộc tính thứ hai truy cập. Trong trường hợp tham số quantityOptions
của thành phần kết hợp StartOrderScreen
, Int
đầu tiên là mã tài nguyên để chuỗi này được xuất hiện trên từng nút. Int
thứ hai là số lượng bánh thực tế.
Chúng ta sẽ truyền thuộc tính thứ hai của cặp đã chọn khi gọi hàm onNextButtonClicked()
.
- Tìm một biểu thức lambda trống cho tham số
onClick
củaSelectQuantityButton
.
quantityOptions.forEach { item ->
SelectQuantityButton(
labelResourceId = item.first,
onClick = {}
)
}
- Trong biểu thức lambda, hãy gọi
onNextButtonClicked
, truyền vàoitem.second
(số lượng bánh nướng).
quantityOptions.forEach { item ->
SelectQuantityButton(
labelResourceId = item.first,
onClick = { onNextButtonClicked(item.second) }
)
}
Thêm trình xử lý nút vào SelectOptionScreen
- Bên dưới tham số
onSelectionChanged
của thành phần kết hợpSelectOptionScreen
trongSelectOptionScreen.kt
, hãy thêm một tham số có tên làonCancelButtonClicked
thuộc kiểu() -> Unit
chứa giá trị mặc định của{}
.
@Composable
fun SelectOptionScreen(
subtotal: String,
options: List<String>,
onSelectionChanged: (String) -> Unit = {},
onCancelButtonClicked: () -> Unit = {},
modifier: Modifier = Modifier
)
- Bên dưới tham số
onCancelButtonClicked
, hãy thêm một tham số khác thuộc kiểu() -> Unit
có tênonNextButtonClicked
chứa giá trị mặc định của{}
.
@Composable
fun SelectOptionScreen(
subtotal: String,
options: List<String>,
onSelectionChanged: (String) -> Unit = {},
onCancelButtonClicked: () -> Unit = {},
onNextButtonClicked: () -> Unit = {},
modifier: Modifier = Modifier
)
- Truyền
onCancelButtonClicked
cho tham sốonClick
của nút huỷ.
OutlinedButton(
modifier = Modifier.weight(1f),
onClick = onCancelButtonClicked
) {
Text(stringResource(R.string.cancel))
}
- Truyền
onNextButtonClicked
cho tham sốonClick
của nút tiếp theo.
Button(
modifier = Modifier.weight(1f),
enabled = selectedValue.isNotEmpty(),
onClick = onNextButtonClicked
) {
Text(stringResource(R.string.next))
}
Thêm trình xử lý nút vào SummaryScreen
Cuối cùng, hãy thêm các hàm xử lý nút cho nút Cancel (Huỷ) và nút Send (Gửi) trên màn hình tóm tắt.
- Trong thành phần kết hợp
OrderSummaryScreen
trongSummaryScreen.kt
, hãy thêm một tham số có tên làonCancelButtonClicked
thuộc loại() -> Unit
.
@Composable
fun OrderSummaryScreen(
orderUiState: OrderUiState,
onCancelButtonClicked: () -> Unit,
modifier: Modifier = Modifier
){
...
}
- Thêm một tham số khác thuộc loại
(String, String) -> Unit
rồi đặt tên cho tham số này làonSendButtonClicked
.
@Composable
fun OrderSummaryScreen(
orderUiState: OrderUiState,
onCancelButtonClicked: () -> Unit,
onSendButtonClicked: (String, String) -> Unit,
modifier: Modifier = Modifier
){
...
}
- Thành phần
OrderSummaryScreen
giờ đây chờ đợi các giá trị dành choonSendButtonClicked
vàonCancelButtonClicked
. TìmOrderSummaryPreview
, truyền nội dung lambda trống chứa hai tham sốString
đếnonSendButtonClicked
và một nội dung lambda trống đến tham sốonCancelButtonClicked
.
@Preview
@Composable
fun OrderSummaryPreview() {
CupcakeTheme {
OrderSummaryScreen(
orderUiState = OrderUiState(0, "Test", "Test", "$300.00"),
onSendButtonClicked = { subject: String, summary: String -> },
onCancelButtonClicked = {},
modifier = Modifier.fillMaxHeight()
)
}
}
- Truyền
onSendButtonClicked
cho tham sốonClick
của nút Send (Gửi). Truyền vàonewOrder
vàorderSummary
, là hai biến được xác định trước đó trongOrderSummaryScreen
. Các chuỗi này bao gồm dữ liệu thực tế mà người dùng có thể chia sẻ với ứng dụng khác.
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onSendButtonClicked(newOrder, orderSummary) }
) {
Text(stringResource(R.string.send))
}
- Truyền
onCancelButtonClicked
cho tham sốonClick
của nút Cancel (Huỷ).
OutlinedButton(
modifier = Modifier.fillMaxWidth(),
onClick = onCancelButtonClicked
) {
Text(stringResource(R.string.cancel))
}
Điều hướng đến một tuyến khác
Để điều hướng đến một tuyến khác, bạn chỉ cần gọi phương thức navigate()
trên bản sao NavHostController
.
Phương thức điều hướng sẽ lấy một tham số duy nhất: một String
tương ứng với một tuyến đã xác định trong NavHost
. Nếu tuyến này khớp với một trong các lệnh gọi đến composable()
trong NavHost
, thì ứng dụng sẽ điều hướng đến màn hình đó.
Bạn sẽ truyền các hàm gọi navigate()
khi người dùng nhấn vào các nút trên màn hình Start
, Flavor
và Pickup
.
- Trong
CupcakeScreen.kt
, hãy tìm lệnh gọi đếncomposable()
cho màn hình bắt đầu. Đối với tham sốonNextButtonClicked
, hãy truyền vào biểu thức lambda.
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
onNextButtonClicked = {
}
)
Bạn có nhớ thuộc tính Int
được truyền vào hàm này cho số lượng bánh nướng nhỏ không? Trước khi chuyển đến màn hình tiếp theo, bạn nên cập nhật mô hình hiển thị để ứng dụng cho thấy tổng giá tiền chính xác.
- Gọi
setQuantity
trênviewModel
, truyền vàoit
.
onNextButtonClicked = {
viewModel.setQuantity(it)
}
- Gọi
navigate()
trênnavController
, truyền vàoCupcakeScreen.Flavor.name
choroute
.
onNextButtonClicked = {
viewModel.setQuantity(it)
navController.navigate(CupcakeScreen.Flavor.name)
}
- Đối với tham số
onNextButtonClicked
trên màn hình hương vị, bạn chỉ cần truyền vào một hàm lambda gọinavigate()
, truyền vàoCupcakeScreen.Pickup.name
choroute
.
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Pickup.name) },
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
}
- Truyền vào một hàm lambda trống cho
onCancelButtonClicked
mà bạn sẽ triển khai tiếp theo.
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Pickup.name) },
onCancelButtonClicked = {},
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
- Đối với tham số
onNextButtonClicked
trên màn hình đến lấy hàng, hãy truyền vào một hàm lambda gọinavigate()
, truyền vàoCupcakeScreen.Summary.name
choroute
.
composable(route = CupcakeScreen.Pickup.name) {
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Summary.name) },
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) },
modifier = Modifier.fillMaxHeight()
)
}
- Một lần nữa, truyền vào một hàm lambda trống cho
onCancelButtonClicked()
.
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Summary.name) },
onCancelButtonClicked = {},
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) },
modifier = Modifier.fillMaxHeight()
)
- Đối với
OrderSummaryScreen
, hãy truyền các hàm lambda trống vàoonCancelButtonClicked
vàonSendButtonClicked
. Thêm các tham số chosubject
vàsummary
đã được truyền vàoonSendButtonClicked
. Bạn sẽ sớm triển khai các tham số này.
composable(route = CupcakeScreen.Summary.name) {
OrderSummaryScreen(
orderUiState = uiState,
onCancelButtonClicked = {},
onSendButtonClicked = { subject: String, summary: String ->
},
modifier = Modifier.fillMaxHeight()
)
}
Bạn hiện có thể điều hướng qua từng màn hình của ứng dụng. Lưu ý khi gọi navigate()
, màn hình không chỉ thay đổi mà còn được đặt ở đầu ngăn xếp lui. Ngoài ra, khi nhấn vào nút quay lại trên hệ thống, bạn có thể quay lại màn hình trước đó.
Ứng dụng xếp chồng từng màn hình lên trên màn hình trước đó và nút quay lại () có thể xoá những màn hình trên. Quá trình của màn hình từ startDestination
dưới cùng đến màn hình trên cùng vừa xuất hiện được gọi là ngăn xếp lui (back stack).
Chuyển đến màn hình bắt đầu
Không giống như nút quay lại của hệ thống, nút Cancel (Huỷ) không quay lại màn hình trước đó. Thay vào đó, thao tác này sẽ bật lên, loại bỏ tất cả màn hình khỏi ngăn xếp lui rồi quay lại màn hình bắt đầu.
Bạn có thể thực hiện việc này bằng cách gọi phương thức popBackStack()
.
Phương thức popBackStack()
có 2 tham số bắt buộc.
route
: Chuỗi biểu thị tuyến của đích đến mà bạn muốn quay lại.inclusive
: Một giá trị Boolean mà nếu đúng thì giá trị này sẽ bật lên (xoá) tuyến được chỉ định. Nếu giá trị sai thìpopBackStack()
sẽ loại bỏ tất cả đích đến ở trên cùng nhưng không bao gồm đích đến bắt đầu, để đích đến này trở thành màn hình trên cùng mà người dùng thấy được.
Khi người dùng nhấn nút Cancel (Huỷ) trên bất kỳ màn hình nào, ứng dụng sẽ đặt lại trạng thái trong mô hình chế độ xem và gọi popBackStack()
. Trước tiên, bạn sẽ triển khai một phương thức để thực hiện việc này, sau đó truyền phương thức này vào tham số thích hợp trên cả ba màn hình bằng nút Cancel (Huỷ).
- Sau hàm
CupcakeApp()
, hãy xác định một hàm riêng có tên làcancelOrderAndNavigateToStart()
.
private fun cancelOrderAndNavigateToStart() {
}
- Thêm hai tham số:
viewModel
thuộc loạiOrderViewModel
vànavController
thuộc loạiNavHostController
.
private fun cancelOrderAndNavigateToStart(
viewModel: OrderViewModel,
navController: NavHostController
) {
}
- Trong phần nội dung hàm, hãy gọi
resetOrder()
trênviewModel
.
private fun cancelOrderAndNavigateToStart(
viewModel: OrderViewModel,
navController: NavHostController
) {
viewModel.resetOrder()
}
- Gọi
popBackStack()
trênnavController
, truyền vàoCupcakeScreen.Start.name
choroute
vàfalse
choinclusive
.
private fun cancelOrderAndNavigateToStart(
viewModel: OrderViewModel,
navController: NavHostController
) {
viewModel.resetOrder()
navController.popBackStack(CupcakeScreen.Start.name, inclusive = false)
}
- Trong thành phần kết hợp
CupcakeApp()
, truyềncancelOrderAndNavigateToStart
vào tham sốonCancelButtonClicked
của hai thành phần kết hợpSelectOptionScreen
vàOrderSummaryScreen
.
composable(route = CupcakeScreen.Start.name) {
StartOrderScreen(
quantityOptions = DataSource.quantityOptions,
onNextButtonClicked = {
viewModel.setQuantity(it)
navController.navigate(CupcakeScreen.Flavor.name)
},
modifier = Modifier
.fillMaxSize()
.padding(dimensionResource(R.dimen.padding_medium))
)
}
composable(route = CupcakeScreen.Flavor.name) {
val context = LocalContext.current
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Pickup.name) },
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
options = DataSource.flavors.map { id -> context.resources.getString(id) },
onSelectionChanged = { viewModel.setFlavor(it) },
modifier = Modifier.fillMaxHeight()
)
}
composable(route = CupcakeScreen.Pickup.name) {
SelectOptionScreen(
subtotal = uiState.price,
onNextButtonClicked = { navController.navigate(CupcakeScreen.Summary.name) },
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
options = uiState.pickupOptions,
onSelectionChanged = { viewModel.setDate(it) },
modifier = Modifier.fillMaxHeight()
)
}
composable(route = CupcakeScreen.Summary.name) {
OrderSummaryScreen(
orderUiState = uiState,
onCancelButtonClicked = {
cancelOrderAndNavigateToStart(viewModel, navController)
},
onSendButtonClicked = { subject: String, summary: String ->
},
modifier = Modifier.fillMaxHeight()
)
}
- Chạy ứng dụng và kiểm thử để đảm bảo việc nhấn nút Cancel (Huỷ) trên mọi màn hình cũng sẽ điều hướng người dùng quay lại màn hình đầu tiên.
6. Chuyển đến ứng dụng khác
Cho đến nay, bạn đã học cách điều hướng đến một màn hình khác trong ứng dụng và cách quay lại màn hình chính. Chỉ còn một bước nữa thôi là bạn có thể triển khai tính năng điều hướng trong ứng dụng Cupcake. Trên màn hình tóm tắt đơn đặt hàng, người dùng có thể gửi đơn đặt hàng của mình đến một ứng dụng khác. Lựa chọn này sẽ hiện ShareSheet (một thành phần giao diện người dùng che phủ phần dưới cùng của màn hình) cho thấy các lựa chọn chia sẻ.
Phần giao diện người dùng này không thuộc ứng dụng Cupcake. Trên thực tế, nó thuộc giao diện của hệ điều hành Android. Giao diện người dùng hệ thống, chẳng hạn như màn hình chia sẻ, không được gọi bởi navController
của bạn. Thay vào đó, bạn sẽ dùng một tham số có tên là Ý định.
Ý định là một yêu cầu để hệ thống thực hiện một số hành động, thường là một hoạt động mới. Có nhiều ý định khác nhau và bạn nên tham khảo tài liệu để biết danh sách các ý định đầy đủ. Tuy nhiên, chúng ta quan tâm đến một ý định gọi là ACTION_SEND
. Bạn có thể cung cấp ý định này bằng một số dữ liệu, chẳng hạn như một chuỗi và trình bày các thao tác chia sẻ phù hợp cho dữ liệu đó.
Quy trình thiết lập một ý định cơ bản như sau:
- Tạo một đối tượng có ý định và chỉ định ý định đó, chẳng hạn như
ACTION_SEND
. - Chỉ định loại dữ liệu bổ sung đang được gửi cùng với ý định. Đối với một đoạn văn bản đơn giản, bạn có thể sử dụng
"text/plain"
. Tuy nhiên, bạn cũng có thể dùng các loại văn bản có sẵn khác, chẳng hạn như"image/*"
hoặc"video/*"
. - Truyền mọi dữ liệu bổ sung vào ý định, chẳng hạn như văn bản hoặc hình ảnh cần chia sẻ, bằng cách gọi phương thức
putExtra()
. Ý định này sẽ có hai phần bổ sung làEXTRA_SUBJECT
vàEXTRA_TEXT
. - Gọi phương thức
startActivity()
trong ngữ cảnh, truyền một hoạt động được tạo từ ý định.
Chúng tôi sẽ hướng dẫn bạn cách tạo ý định của hành động chia sẻ, nhưng quy trình này cũng giống như đối với các loại ý định khác. Đối với các dự án trong tương lai, bạn nên tham khảo tài liệu nếu cần để biết loại dữ liệu cụ thể và các dữ liệu bổ sung cần thiết.
Hoàn thành các bước sau để tạo ý định gửi đơn đặt hàng bánh nướng đến một ứng dụng khác:
- Trong CupcakeScreen.kt, bên dưới thành phần kết hợp
CupcakeApp
, hãy tạo một hàm riêng có tên làshareOrder()
.
private fun shareOrder()
- Thêm tham số có tên là
context
thuộc loạiContext
.
import android.content.Context
private fun shareOrder(context: Context) {
}
- Thêm hai tham số
String
làsubject
vàsummary
. Các chuỗi này sẽ hiển thị trên trang hành động chia sẻ.
private fun shareOrder(context: Context, subject: String, summary: String) {
}
- Trong phần nội dung của hàm, hãy tạo một Ý định có tên
intent
và truyềnIntent.ACTION_SEND
dưới dạng đối số.
import android.content.Intent
val intent = Intent(Intent.ACTION_SEND)
Vì chỉ cần định cấu hình cho đối tượng Intent
một lần, nên bạn có thể rút gọn các dòng mã tiếp theo bằng hàm apply()
đã tìm hiểu trong lớp học lập trình trước đó.
- Gọi
apply()
trên Ý định mới được tạo và truyền vào một biểu thức lambda.
val intent = Intent(Intent.ACTION_SEND).apply {
}
- Trong nội dung hàm lambda, hãy đặt loại thành
"text/plain"
. Vì đang thực hiện thao tác này trong một hàm được truyền vàoapply()
, nên bạn không cần tham chiếu đến giá trị nhận dạng của đối tượng làintent
.
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
}
- Gọi
putExtra()
, truyền tiêu đề choEXTRA_SUBJECT
.
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_SUBJECT, subject)
}
- Gọi
putExtra()
, truyền tóm tắt choEXTRA_TEXT
.
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_SUBJECT, subject)
putExtra(Intent.EXTRA_TEXT, summary)
}
- Gọi phương thức ngữ cảnh
startActivity()
.
context.startActivity(
)
- Trong lambda được truyền vào
startActivity()
, hãy tạo một hoạt động từ Ý định bằng cách gọi phương thức lớpcreateChooser()
. Truyền ý định cho đối số đầu tiên và tài nguyên chuỗinew_cupcake_order
.
context.startActivity(
Intent.createChooser(
intent,
context.getString(R.string.new_cupcake_order)
)
)
- Tại thành phần kết hợp
CupcakeApp
, trong lệnh gọi đếncomposable()
choCucpakeScreen.Summary.name
, lấy thông tin tham chiếu đến đối tượng ngữ cảnh để bạn có thể truyền cho hàmshareOrder()
.
composable(route = CupcakeScreen.Summary.name) {
val context = LocalContext.current
...
}
- Trong phần thân hàm lambda của
onSendButtonClicked()
, gọishareOrder()
, truyền vàocontext
,subject
vàsummary
làm đối số.
onSendButtonClicked = { subject: String, summary: String ->
shareOrder(context, subject = subject, summary = summary)
}
- Chạy ứng dụng và điều hướng qua các màn hình.
Khi nhấp vào Send Order to Another App (Gửi đơn đặt hàng đến một ứng dụng khác), bạn sẽ thấy thao tác chia sẻ như Messaging (Nhắn tin) và Bluetooth ở bảng dưới cùng, cùng với tiêu đề và phần tóm tắt mà bạn đã cung cấp dưới dạng bổ sung.
7. Làm cho thanh ứng dụng phản hồi tính năng điều hướng
Mặc dù ứng dụng đã hoạt động cũng như có thể điều hướng đi và đến qua mọi màn hình, nhiều nội dung trong ảnh chụp màn hình ở đầu lớp học lập trình vẫn còn chưa được nhắc đến. Thanh ứng dụng không thể tự động phản hồi tính năng điều hướng. Tiêu đề không tự cập nhật mỗi khi ứng dụng điều hướng đến một tuyến mới, cũng như không hiện nút Mũi tên lên trước tiêu đề khi thích hợp.
Đoạn mã khởi đầu chứa một thành phần kết hợp để quản lý AppBar
có tên là CupcakeAppBar
. Bây giờ, sau khi đã triển khai tính năng điều hướng trong ứng dụng, bạn có thể sử dụng thông tin của ngăn xếp lui để cho thấy tiêu đề chính xác và hiện nút Mũi tên lên khi thích hợp. Thành phần kết hợp CupcakeAppBar
cần nhận biết màn hình hiện tại để có thể cập nhật tiêu đề phù hợp.
- Ở enum
CupcakeScreen
trong CupcakeScreen.kt, hãy thêm tham số thuộc kiểuInt
có têntitle
bằng chú thích@StringRes
.
import androidx.annotation.StringRes
enum class CupcakeScreen(@StringRes val title: Int) {
Start,
Flavor,
Pickup,
Summary
}
- Thêm giá trị tài nguyên cho mỗi trường hợp enum, tương ứng với văn bản của tiêu đề cho mỗi màn hình. Dùng
app_name
cho màn hìnhStart
,choose_flavor
cho màn hìnhFlavor
,choose_pickup_date
cho màn hìnhPickup
vàorder_summary
cho màn hìnhSummary
.
enum class CupcakeScreen(@StringRes val title: Int) {
Start(title = R.string.app_name),
Flavor(title = R.string.choose_flavor),
Pickup(title = R.string.choose_pickup_date),
Summary(title = R.string.order_summary)
}
- Thêm tham số có tên
currentScreen
thuộc kiểuCupcakeScreen
cho thành phần kết hợpCupcakeAppBar
.
fun CupcakeAppBar(
currentScreen: CupcakeScreen,
canNavigateBack: Boolean,
navigateUp: () -> Unit = {},
modifier: Modifier = Modifier
)
- Bên trong
CupcakeAppBar
, hãy thay thế tên ứng dụng được cố định giá trị trong mã với tiêu đề của màn hình hiện tại bằng cách truyềncurrentScreen.title
vào cho lệnh gọi đếnstringResource()
cho tiêu đề tham số củaTopAppBar
.
TopAppBar(
title = { Text(stringResource(currentScreen.title)) },
modifier = modifier,
navigationIcon = {
if (canNavigateBack) {
IconButton(onClick = navigateUp) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(R.string.back_button)
)
}
}
}
)
Nút Mũi tên lên chỉ xuất hiện khi có một thành phần kết hợp trong ngăn xếp lui. Nếu ứng dụng không có màn hình nào trong ngăn xếp lui (StartOrderScreen
xuất hiện) thì nút Mũi tên lên sẽ không xuất hiện. Để kiểm tra điều này, bạn cần tham chiếu đến ngăn xếp lui.
- Trong thành phần kết hợp
CupcakeApp
, bên dưới biếnnavController
, hãy tạo một biến có tênbackStackEntry
rồi gọi phương thứccurrentBackStackEntryAsState()
củanavController
bằng cách sử dụng phương thức uỷ quyềnby
.
import androidx.navigation.compose.currentBackStackEntryAsState
@Composable
fun CupcakeApp(
viewModel: OrderViewModel = viewModel(),
navController: NavHostController = rememberNavController()
){
val backStackEntry by navController.currentBackStackEntryAsState()
...
}
- Chuyển đổi tiêu đề màn hình hiện tại đến một giá trị của
CupcakeScreen
. Bên dưới biếnbackStackEntry
, hãy tạo một biến bằngval
có tên làcurrentScreen
bằng với kết quả của việc gọi lớp hàmvalueOf()
củaCupcakeScreen
, rồi truyền vào tuyến của đích đếnbackStackEntry
. Dùng toán tử elvis để cung cấp một giá trị mặc định củaCupcakeScreen.Start.name
.
val currentScreen = CupcakeScreen.valueOf(
backStackEntry?.destination?.route ?: CupcakeScreen.Start.name
)
- Truyền giá trị của biến
currentScreen
vào tham số có cùng tên của thành phần kết hợpCupcakeAppBar
.
CupcakeAppBar(
currentScreen = currentScreen,
canNavigateBack = false,
navigateUp = {}
)
Miễn là có một màn hình phía sau màn hình hiện tại trong ngăn xếp lui thì nút Mũi tên lên sẽ xuất hiện. Bạn có thể sử dụng biểu thức boolean để xác định xem nút Mũi tên lên có xuất hiện hay không.
- Đối với tham số
canNavigateBack
, hãy truyền vào biểu thức boolean để kiểm tra xem thuộc tínhpreviousBackStackEntry
củanavController
có bằng với giá trị rỗng hay không.
canNavigateBack = navController.previousBackStackEntry != null,
- Gọi phương thức
navigateUp()
củanavController
để quay lại màn hình trước đó.
navigateUp = { navController.navigateUp() }
- Chạy ứng dụng của bạn.
Lưu ý rằng tiêu đề AppBar
hiện đã cập nhật để phản ánh màn hình hiện tại. Khi di chuyển đến một màn hình khác màn hình StartOrderScreen
, nút Mũi tên lên sẽ xuất hiện rồi đưa bạn về màn hình trước.
8. Lấy mã nguồn giải pháp
Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh git sau:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-cupcake.git $ cd basic-android-kotlin-compose-training-cupcake $ git checkout navigation
Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.
Nếu bạn muốn tham khảo mã giải pháp cho lớp học lập trình này, hãy xem trên GitHub.
9. Tóm tắt
Xin chúc mừng! Bạn vừa chuyển từ ứng dụng có một màn hình đơn giản sang ứng dụng phức tạp với nhiều màn hình bằng cách dùng thành phần Điều hướng trong Jetpack để di chuyển qua nhiều màn hình. Bạn đã xác định tuyến, xử lý các tuyến đó trong NavHost và sử dụng tham số loại hàm để tách logic điều hướng khỏi các màn hình riêng lẻ. Bạn cũng đã tìm hiểu cách gửi dữ liệu đến một ứng dụng khác bằng ý định cũng như tuỳ chỉnh thanh ứng dụng để phản hồi thao tác điều hướng. Trong các bài sau, bạn sẽ tiếp tục sử dụng những kỹ năng này khi làm việc trên một số ứng dụng nhiều màn hình khác với độ phức tạp ngày càng tăng.