Di chuyển giữa các màn hình bằng tính năng Compose

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 RowColumn 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ẻ.

13bde33712e135a4.png

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.

  1. Trong CupcakeScreen.kt, phía trên thành phần kết hợp CupcakeAppBar, hãy thêm một lớp enum có tên CupcakeScreen.
enum class CupcakeScreen() {

}
  1. Thêm 4 trường hợp vào lớp enum: Start, Flavor, PickupSummary.
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.

fae7688d6dd53de9.png

Có hai tham số đáng chú ý.

  • navController: Một bản sao của lớp NavHostController. 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ức navigate() để điều hướng đến một đích đến khác. Bạn có thể lấy NavHostController bằng cách gọi rememberNavController() 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ện NavHost lần đầu tiên. Trong trường hợp ứng dụng Cupcake, đây phải là tuyến Start.

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().

  1. Mở CupcakeScreen.kt.
  2. Trong Scaffold, bên dưới biến uiState, hãy thêm một thành phần kết hợp NavHost.
import androidx.navigation.compose.NavHost

Scaffold(
    ...
) { innerPadding ->
    val uiState by viewModel.uiState.collectAsState()

    NavHost()
}
  1. Truyền biến navController cho tham số navControllerCupcakeScreen.Start.name cho tham số startDestination. Truyền đối tượng sửa đổi đã truyền vào CupcakeApp() 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.

f67974b7fb3f0377.png

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ố enum CupcakeScreen.
  • 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.

  1. Gọi hàm composable(), truyền CupcakeScreen.Start.name cho route.
import androidx.navigation.compose.composable

NavHost(
    navController = navController,
    startDestination = CupcakeScreen.Start.name,
    modifier = Modifier.padding(innerPadding)
) {
    composable(route = CupcakeScreen.Start.name) {

    }
}
  1. Trong trailing lambda (lambda theo sau), gọi thành phần kết hợp StartOrderScreen để truyền quantityOptions cho thuộc tính quantityOptions. Đối với modifier, hãy truyền Modifier.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))
        )
    }
}
  1. Bên dưới lệnh gọi đầu tiên đến composable(), hãy gọi lại composable(), truyền CupcakeScreen.Flavor.name cho route.
composable(route = CupcakeScreen.Flavor.name) {

}
  1. 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
}
  1. Gọi thành phần kết hợp SelectOptionScreen.
composable(route = CupcakeScreen.Flavor.name) {
    val context = LocalContext.current
    SelectOptionScreen(

    )
}
  1. 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
    )
}
  1. 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ọi context.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) }
    )
}
  1. Đối với tham số onSelectionChanged, hãy truyền biểu thức lambda gọi setFlavor() trên mô hình khung hiển thị, truyền it (đối số đã truyền vào onSelectionChanged()). Đối với tham số modifier, hãy truyền Modifier.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.

  1. Gọi lại hàm composable(), truyền CupcakeScreen.Pickup.name cho tham số route.
composable(route = CupcakeScreen.Pickup.name) {

}
  1. Trong trailing lambda, hãy gọi thành phần kết hợp SelectOptionScreen và truyền uiState.price vào subtotal như trước. Truyền uiState.pickupOptions cho tham số options và biểu thức lambda gọi setDate() trên viewModel cho tham số onSelectionChanged. Đối với tham số modifier, hãy truyền Modifier.fillMaxHeight().
SelectOptionScreen(
    subtotal = uiState.price,
    options = uiState.pickupOptions,
    onSelectionChanged = { viewModel.setDate(it) },
    modifier = Modifier.fillMaxHeight()
)
  1. Gọi composable() lại một lần nữa, truyền CupcakeScreen.Summary.name cho route.
composable(route = CupcakeScreen.Summary.name) {

}
  1. Trong trailing lambda, hãy gọi thành phần kết hợp OrderSummaryScreen(), truyền biến uiState cho tham số orderUiState. Đối với tham số modifier, hãy truyền Modifier.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.

  1. Mở StartOrderScreen.kt.
  2. 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
){
    ...
}
  1. Giờ đây, khi thành phần kết hợp StartOrderScreen chờ đợi một giá trị dành cho onNextButtonClicked, hãy tìm StartOrderPreview 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.

  1. 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.

8326701a77706258.png

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().

  1. Tìm một biểu thức lambda trống cho tham số onClick của SelectQuantityButton.
quantityOptions.forEach { item ->
    SelectQuantityButton(
        labelResourceId = item.first,
        onClick = {}
    )
}
  1. Trong biểu thức lambda, hãy gọi onNextButtonClicked, truyền vào item.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

  1. Bên dưới tham số onSelectionChanged của thành phần kết hợp SelectOptionScreen trong SelectOptionScreen.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
)
  1. Bên dưới tham số onCancelButtonClicked, hãy thêm một tham số khác thuộc kiểu () -> Unit có tên onNextButtonClicked 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
)
  1. Truyền onCancelButtonClicked cho tham số onClick của nút huỷ.
OutlinedButton(
    modifier = Modifier.weight(1f),
    onClick = onCancelButtonClicked
) {
    Text(stringResource(R.string.cancel))
}
  1. 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.

  1. Trong thành phần kết hợp OrderSummaryScreen trong SummaryScreen.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
){
    ...
}
  1. 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
){
    ...
}
  1. Thành phần OrderSummaryScreen giờ đây chờ đợi các giá trị dành cho onSendButtonClickedonCancelButtonClicked. Tìm OrderSummaryPreview, truyền nội dung lambda trống chứa hai tham số String đến onSendButtonClicked 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()
       )
   }
}
  1. Truyền onSendButtonClicked cho tham số onClick của nút Send (Gửi). Truyền vào newOrderorderSummary, là hai biến được xác định trước đó trong OrderSummaryScreen. 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))
}
  1. 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, bạn chỉ cần gọi phương thức navigate() trên bản sao NavHostController.

fc8aae3911a6a25d.png

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, FlavorPickup.

  1. Trong CupcakeScreen.kt, hãy tìm lệnh gọi đến composable() 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.

  1. Gọi setQuantity trên viewModel, truyền vào it.
onNextButtonClicked = {
    viewModel.setQuantity(it)
}
  1. Gọi navigate() trên navController, truyền vào CupcakeScreen.Flavor.name cho route.
onNextButtonClicked = {
    viewModel.setQuantity(it)
    navController.navigate(CupcakeScreen.Flavor.name)
}
  1. Đố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ọi navigate(), truyền vào CupcakeScreen.Pickup.name cho route.
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()
    )
}
  1. 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()
)
  1. Đố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ọi navigate(), truyền vào CupcakeScreen.Summary.name cho route.
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()
    )
}
  1. 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()
)
  1. Đối với OrderSummaryScreen, hãy truyền các hàm lambda trống vào onCancelButtonClickedonSendButtonClicked. Thêm các tham số cho subjectsummary đã được truyền vào onSendButtonClicked. 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 (bade5f3ecb71e4a2.png) 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().

2f382e5eb319b4b8.png

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ỷ).

  1. Sau hàm CupcakeApp(), hãy xác định một hàm riêng có tên là cancelOrderAndNavigateToStart().
private fun cancelOrderAndNavigateToStart() {
}
  1. Thêm hai tham số: viewModel thuộc loại OrderViewModelnavController thuộc loại NavHostController.
private fun cancelOrderAndNavigateToStart(
    viewModel: OrderViewModel,
    navController: NavHostController
) {
}
  1. Trong phần nội dung hàm, hãy gọi resetOrder() trên viewModel.
private fun cancelOrderAndNavigateToStart(
    viewModel: OrderViewModel,
    navController: NavHostController
) {
    viewModel.resetOrder()
}
  1. Gọi popBackStack() trên navController, truyền vào CupcakeScreen.Start.name cho routefalse cho inclusive.
private fun cancelOrderAndNavigateToStart(
    viewModel: OrderViewModel,
    navController: NavHostController
) {
    viewModel.resetOrder()
    navController.popBackStack(CupcakeScreen.Start.name, inclusive = false)
}
  1. Trong thành phần kết hợp CupcakeApp(), truyền cancelOrderAndNavigateToStart vào tham số onCancelButtonClicked của hai thành phần kết hợp SelectOptionScreenOrderSummaryScreen.
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()
   )
}
  1. 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:

  1. Tạo một đối tượng có ý định và chỉ định ý định đó, chẳng hạn như ACTION_SEND.
  2. 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/*".
  3. 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_SUBJECTEXTRA_TEXT.
  4. 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:

  1. 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()
  1. Thêm tham số có tên là context thuộc loại Context.
import android.content.Context

private fun shareOrder(context: Context) {
}
  1. Thêm hai tham số Stringsubjectsummary. 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) {
}
  1. Trong phần nội dung của hàm, hãy tạo một Ý định có tên intent và truyền Intent.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 đó.

  1. 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 {

}
  1. 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ào apply(), 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"
}
  1. Gọi putExtra(), truyền tiêu đề cho EXTRA_SUBJECT.
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_SUBJECT, subject)
}
  1. Gọi putExtra(), truyền tóm tắt cho EXTRA_TEXT.
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_SUBJECT, subject)
    putExtra(Intent.EXTRA_TEXT, summary)
}
  1. Gọi phương thức ngữ cảnh startActivity().
context.startActivity(

)
  1. 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ớp createChooser(). Truyền ý định cho đối số đầu tiên và tài nguyên chuỗi new_cupcake_order.
context.startActivity(
    Intent.createChooser(
        intent,
        context.getString(R.string.new_cupcake_order)
    )
)
  1. Tại thành phần kết hợp CupcakeApp, trong lệnh gọi đến composable() cho CucpakeScreen.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àm shareOrder().
composable(route = CupcakeScreen.Summary.name) {
    val context = LocalContext.current

    ...
}
  1. Trong phần thân hàm lambda của onSendButtonClicked(), gọi shareOrder(), truyền vào context, subjectsummary làm đối số.
onSendButtonClicked = { subject: String, summary: String ->
    shareOrder(context, subject = subject, summary = summary)
}
  1. 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.

13bde33712e135a4.png

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.

  1. Ở enum CupcakeScreen trong CupcakeScreen.kt, hãy thêm tham số thuộc kiểu Int có tên title bằng chú thích @StringRes.
import androidx.annotation.StringRes

enum class CupcakeScreen(@StringRes val title: Int) {
    Start,
    Flavor,
    Pickup,
    Summary
}
  1. 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ình Start, choose_flavor cho màn hình Flavor, choose_pickup_date cho màn hình Pickuporder_summary cho màn hình Summary.
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)
}
  1. Thêm tham số có tên currentScreen thuộc kiểu CupcakeScreen cho thành phần kết hợp CupcakeAppBar.
fun CupcakeAppBar(
    currentScreen: CupcakeScreen,
    canNavigateBack: Boolean,
    navigateUp: () -> Unit = {},
    modifier: Modifier = Modifier
)
  1. 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ền currentScreen.title vào cho lệnh gọi đến stringResource() cho tiêu đề tham số của TopAppBar.
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.

  1. Trong thành phần kết hợp CupcakeApp, bên dưới biến navController, hãy tạo một biến có tên backStackEntry rồi gọi phương thức currentBackStackEntryAsState() của navController bằng cách sử dụng phương thức uỷ quyền by.
import androidx.navigation.compose.currentBackStackEntryAsState

@Composable
fun CupcakeApp(
    viewModel: OrderViewModel = viewModel(),
    navController: NavHostController = rememberNavController()
){

    val backStackEntry by navController.currentBackStackEntryAsState()

    ...
}
  1. 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ến backStackEntry, hãy tạo một biến bằng val có tên là currentScreen bằng với kết quả của việc gọi lớp hàm valueOf() của CupcakeScreen, rồi truyền vào tuyến của đích đến backStackEntry. Dùng toán tử elvis để cung cấp một giá trị mặc định của CupcakeScreen.Start.name.
val currentScreen = CupcakeScreen.valueOf(
    backStackEntry?.destination?.route ?: CupcakeScreen.Start.name
)
  1. 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ợp CupcakeAppBar.
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.

  1. Đối với tham số canNavigateBack, hãy truyền vào biểu thức boolean để kiểm tra xem thuộc tính previousBackStackEntry của navController có bằng với giá trị rỗng hay không.
canNavigateBack = navController.previousBackStackEntry != null,
  1. Gọi phương thức navigateUp() của navController để quay lại màn hình trước đó.
navigateUp = { navController.navigateUp() }
  1. 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.

3fd023516061f522.gif

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.

Tìm hiểu thêm