Trang này đề cập các phương pháp hay nhất để đảm bảo an toàn về kiểu thời gian chạy cho Navigation Kotlin DSL và Navigation Compose. Nhìn chung thì bạn nên ánh xạ từng màn hình của ứng dụng hoặc biểu đồ điều hướng đến tệp Navigation trên cơ sở từng mô-đun. Mọi tệp kết quả đều phải chứa tất cả thông tin Navigation liên quan cho một đích đến nhất định. Các tệp điều hướng này cũng là nơi đối tượng sửa đổi chế độ hiển thị Kotlin cung cấp sự an toàn về kiểu thời gian chạy:
- Các hàm an toàn về kiểu được hiển thị công khai với phần còn lại của cơ sở mã.
- Các khái niệm cụ thể về Navigation cho một màn hình hoặc biểu đồ điều hướng cụ thể được đặt vào cùng vị trí và giữ riêng trong cùng một tệp để chúng không thể được truy cập vào phần còn lại của cơ sở mã.
Chia tách biểu đồ điều hướng
Bạn nên chia tách biểu đồ điều hướng theo màn hình. Về cơ bản, đây là cách tiếp cận giống như khi chia tách màn hình cho nhiều hàm có khả năng kết hợp. Mỗi màn hình phải có một hàm tiện ích NavGraphBuilder
.
Hàm tiện ích này là cầu nối giữa hàm có khả năng kết hợp cấp màn hình không có trạng thái và logic Điều hướng cụ thể. Lớp này cũng có thể xác định nguồn gốc của trạng thái và cách xử lý sự kiện.
Sau đây là một ConversationScreen
thông thường có thể là internal
đối với mô-đun của chính nó để các mô-đun khác không thể truy cập vào đó:
// ConversationScreen.kt
@Composable
internal fun ConversationScreen(
uiState: ConversationUiState,
onPinConversation: () -> Unit,
onNavigateToParticipantList: (conversationId: String) -> Unit
) { ... }
Hàm tiện ích NavGraphBuilder
sau đây thêm thành phần kết hợp ConversationScreen
làm đích đến của NavGraph
đó. Hàm này cũng kết nối màn hình với ViewModel cung cấp trạng thái giao diện người dùng màn hình và xử lý logic kinh doanh liên quan đến màn hình. Sự kiện Navigation không thể được ViewModel xử lý sẽ được hiển thị cho phương thức gọi.
// ConversationNavigation.kt
private const val conversationIdArg = "conversationId"
// Adds conversation screen to `this` NavGraphBuilder
fun NavGraphBuilder.conversationScreen(
// Navigation events are exposed to the caller to be handled at a higher level
onNavigateToParticipantList: (conversationId: String) -> Unit
) {
composable("conversation/{$conversationIdArg}") {
// The ViewModel as a screen level state holder produces the screen
// UI state and handles business logic for the ConversationScreen
val viewModel: ConversationViewModel = hiltViewModel()
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
ConversationScreen(
uiState,
::viewModel.pinConversation,
onNavigateToParticipantList
)
}
}
Tệp ConversationNavigation.kt
tách mã khỏi thư viện Navigation và chính đích đến này. Tệp này cũng cung cấp thông tin đóng gói về các khái niệm Navigation như tuyến hoặc mã nhận diện đối số được giữ riêng tư vì các mã này không bao giờ được rò rỉ ra bên ngoài tệp này. Các sự kiện Navigation không thể xử lý ở lớp này cần được hiển thị cho phương thức gọi để được xử lý ở cấp độ phù hợp. Bạn sẽ tìm thấy ví dụ những sự kiện như vậy nhờ onNavigateToParticipantList
trong đoạn mã ở trên.
Điều hướng an toàn về kiểu
Navigation Kotlin DS mà Navigation Compose được tạo trên đó hiện không cung cấp chế độ an toàn về kiểu thời gian biên dịch theo loại mà Safe Args cung cấp cho biểu đồ điều hướng tạo trong tệp tài nguyên XML điều hướng. Safe Args tạo mã chứa các lớp và phương thức an toàn về kiểu cho các thao tác và đích đến Navigation. Tuy nhiên, có thể định cấu trúc mã Navigation để được an toàn về kiểu vào thời gian chạy. Nhờ vậy, bạn có thể tránh sự cố và đảm bảo rằng:
- Các đối số mà bạn cung cấp khi di chuyển đến một đích đến hoặc biểu đồ điều hướng đều thuộc loại phù hợp và có đủ tất cả đối số cần thiết.
- Các đối số bạn truy xuất qua
SavedStateHandle
là loại phù hợp.
Điều hướng đến một đích đến
Mỗi đích đến cũng phải hiển thị hàm tiện ích NavController
để cho phép các đích đến khác điều hướng đến nó một cách an toàn.
// ConversationNavigation.kt
fun NavController.navigateToConversation(conversationId: String) {
this.navigate("conversation/$conversationId")
}
Nếu muốn điều hướng đến một màn hình của ứng dụng bằng NavOptions
khác, chẳng hạn như popUpTo, savedState, restoreState
hoặc singleTop
khi điều hướng, truyền một tham số không bắt buộc vào hàm tiện ích NavController
.
// HomeNavigation.kt
const val HomeRoute = "home"
fun NavController.navigateToHome(navOptions: NavOptions? = null) {
this.navigate(HomeRoute, navOptions)
}
Trình bao bọc đối số an toàn về kiểu
Có thể tuỳ ý tạo một trình bao bọc an toàn về kiểu để trích xuất các đối số từ SavedStateHandle
cho ViewModel và từ một NavBackStackEntry
trong nội dung của đích đến để nhận được những lợi ích nêu trong phần giới thiệu về phần này.
// ConversationNavigation.kt
private const val conversationIdArg = "conversationId"
internal class ConversationArgs(val conversationId: String) {
constructor(savedStateHandle: SavedStateHandle) :
this(checkNotNull(savedStateHandle[conversationIdArg]) as String)
}
// ConversationViewModel.kt
internal class ConversationViewModel(...,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val conversationArgs = ConversationArgs(savedStateHandle)
}
Tập hợp biểu đồ điều hướng
Biểu đồ điều hướng sử dụng các hàm tiện ích an toàn về kiểu được mô tả ở trên để thêm đích đến và điều hướng đến các đích đó.
Trong ví dụ sau, đích đến conversation (cuộc trò chuyện) cùng hai đích đến khác là hone (trang chủ) và participant list(danh sách người tham gia) được đưa vào trong một cấp ứng dụng NavHost
như sau:
// MyApp.kt
@Composable
fun MyApp(modifier: Modifier = Modifier) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = HomeRoute,
modifier = modifier
) {
homeScreen(
onNavigateToConversation = { conversationId ->
navController.navigateToConversation(conversationId)
}
)
conversationScreen(
onNavigateToParticipantList = { conversationId ->
navController.navigateToParticipantList(conversationId)
}
}
participantListScreen()
}
An toàn về kiểu trong các biểu đồ điều hướng lồng
Bạn nên chọn chế độ hiển thị phù hợp cho các mô-đun cung cấp nhiều màn hình. Đây là khái niệm tương tự như đối với mỗi phương thức trong các phần trên. Tuy nhiên, việc hiển thị từng màn hình cho các mô-đun khác có thể sẽ không hợp lý. Trong trường hợp đó, bạn nên coi chúng là một phần của một luồng độc lập lớn hơn.
Tập hợp các màn hình độc lập này được gọi là biểu đồ điều hướng lồng. Điều này cho phép đưa nhiều màn hình vào một phương thức tiện ích NavGraphBuilder
duy nhất. Phương thức này sử dụng các phương thức tiện ích NavController
đó để liên kết các màn hình trong cùng một mô-đun với nhau.
Trong ví dụ sau, đích đến của conversation (cuộc trò chuyện), được mô tả trong các phần trước, sẽ xuất hiện ở biểu đồ điều hướng lồng cùng với hai đích đến khác, conversation list (danh sách cuộc trò chuyện) và participant list (danh sách người tham gia):
// ConversationGraphNavigation.kt
private val ConversationGraphRoutePattern = "conversation"
fun NavController.navigateToConversationGraph(navOptions: NavOptions? = null) {
this.navigate(ConversationGraphRoutePattern, navOptions)
}
fun NavGraphBuilder.conversationGraph(navController: NavController) {
navigation(
startDestination = ConversationListRoutePattern,
route = ConversationGraphRoutePattern
) {
conversationListScreen(
onNavigateToConversation = { conversationId ->
navController.navigateToConversation(conversationId)
}
)
conversationScreen(
onNavigateToParticipantList = { conversationId ->
navController.navigateToParticipantList(conversationId)
}
)
partipantList()
}
Có thể sử dụng nhiều biểu đồ điều hướng lồng trong một cấp ứng dụng NavHost
như sau:
// MyApp.kt
@Composable
fun MyApp(modifier: Modifier = Modifier) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = HomeGraphRoutePattern
modifier = modifier
) {
homeGraph(
navController,
onNavigateToConversation = {
navController.navigateToConversationGraph()
}
}
conversationGraph(navController)
}