Mảnh và Kotlin DSL

Thành phần Điều hướng cung cấp một ngôn ngữ đặc thù theo miền dựa trên Kotlin, hoặc DSL, dựa trên tiêu chuẩn an toàn về kiểu của Kotlin trình tạo của Google. API này cho phép bạn khai báo soạn biểu đồ trong mã Kotlin, thay vì hơn là bên trong tài nguyên XML. Điều này có thể hữu ích nếu bạn muốn tạo điều hướng một cách linh động. Ví dụ: ứng dụng của bạn có thể tải xuống và lưu một từ một dịch vụ web bên ngoài rồi sử dụng cấu hình đó để tự động tạo biểu đồ điều hướng trong Hàm onCreate().

Phần phụ thuộc

Để sử dụng Kotlin DSL với các Mảnh, hãy thêm phần phụ thuộc sau vào phần phụ thuộc của ứng dụng Tệp build.gradle:

Groovy

dependencies {
    def nav_version = "2.8.0"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.8.0"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

Tạo biểu đồ

Đây là một ví dụ cơ bản dựa trên thuộc tính Sunflower ứng dụng. Để làm việc này Ví dụ: chúng ta có 2 đích đến: homeplant_detail. Đích đến home xuất hiện khi người dùng khởi chạy ứng dụng lần đầu. Đích đến này sẽ hiển thị danh sách các cây trồng trong vườn của người dùng. Khi người dùng chọn một trong các cây trồng này, ứng dụng sẽ điều hướng đến đích đến plant_detail.

Hình 1 cho thấy những đích đến này cùng với các đối số cần có cho đích đến plant_detail và một thao tác to_plant_detail được ứng dụng sử dụng để di chuyển từ home đến plant_detail.

Ứng dụng Sunflower có hai đích đến cùng thao tác kết nối giữa các đích đến này.
Hình 1. Ứng dụng Sunflower có hai đích đến là homeplant_detail, cùng một thao tác kết nối giữa các đích đến này.

Lưu trữ biểu đồ điều hướng DSL Kotlin

Trước khi tạo biểu đồ điều hướng của ứng dụng, bạn cần một nơi để lưu trữ biểu đồ. Ví dụ bên dưới sẽ sử dụng các mảnh (fragment), do vậy biểu đồ sẽ được lưu trữ trong NavHostFragment ở bên trongFragmentContainerView:

<!-- activity_garden.xml -->
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true" />

</FrameLayout>

Lưu ý rằng thuộc tính app:navGraph không được thiết lập trong ví dụ này. Biểu đồ không được định nghĩa là tài nguyên trong thư mục res/navigation. Do đó, bạn cần đặt thư mục này trong onCreate() trong hoạt động.

Trong XML, một thao tác liên kết mã nhận dạng của một đích đến chứa một hoặc nhiều đối số. Tuy nhiên, khi sử dụng DSL điều hướng, một tuyến có thể chứa các đối số như một phần của tuyến đường đó. Điều này có nghĩa sẽ không có khái niệm về thao tác khi sử dụng DSL.

Bước tiếp theo là xác định các tuyến mà bạn sẽ sử dụng khi xác định biểu đồ.

Tạo tuyến đường cho biểu đồ

Biểu đồ điều hướng dựa trên XML được phân tích cú pháp như một phần của quy trình xây dựng Android. Một hằng số sẽ được tạo cho mỗi id được xác định trong biểu đồ. Các mã tĩnh được tạo trong thời gian xây dựng này không khi tạo biểu đồ điều hướng trong thời gian chạy, để DSL Navigation sử dụng công cụ chuyển đổi tuần tự loại thay vì Mã nhận dạng. Mỗi tuyến đường được biểu thị bằng một loại duy nhất.

Khi xử lý đối số, các đối số này sẽ được tích hợp vào tuyến đường . Điều này cho phép bạn đảm bảo an toàn về kiểu cho các đối số điều hướng của bạn.

@Serializable data object Home
@Serializable data class Plant(val id: String)

Sau khi xác định các tuyến, bạn có thể tạo biểu đồ điều hướng.

val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
    startDestination = Home
) {
    fragment<HomeFragment, Home> {
        label = resources.getString(R.string.home_title)
    }
    fragment<PlantDetailFragment, PlantDetail> {
        label = resources.getString(R.string.plant_detail_title)
    }
}

Trong ví dụ này, hai đích đến của mảnh được xác định bằng cách sử dụng phương thức fragment() Hàm tạo DSL. Hàm này yêu cầu 2 loại đối số của Google.

Trước tiên, một lớp Fragment cung cấp giao diện người dùng cho đích đến này. Việc đặt giá trị này có tác dụng tương tự như đặt thuộc tính android:name trên các đích đến của mảnh đã được xác định bằng XML.

Thứ hai là tuyến đường. Đây phải là một kiểu chuyển đổi tuần tự mở rộng từ Any. Nó phải chứa bất kỳ đối số điều hướng nào mà đích đến này sẽ sử dụng, và loại của chúng.

Hàm này cũng chấp nhận một hàm lambda không bắt buộc cho cấu hình bổ sung, chẳng hạn như làm nhãn đích, cũng như các hàm trình tạo được nhúng cho đối số và liên kết sâu.

Cuối cùng, bạn có thể điều hướng từ home đến plant_detail bằng NavController.navigate() cuộc gọi:

private fun navigateToPlant(plantId: String) {
   findNavController().navigate(route = PlantDetail(id = plantId))
}

Trong PlantDetailFragment, bạn có thể lấy các đối số điều hướng bằng cách lấy hiện tại NavBackStackEntry và gọi điện toRoute trên đó để lấy thực thể tuyến đường.

val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

Nếu PlantDetailFragment đang sử dụng ViewModel, hãy lấy thực thể tuyến bằng cách sử dụng SavedStateHandle.toRoute.

val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

Phần còn lại của hướng dẫn này sẽ mô tả các thành phần của biểu đồ điều hướng phổ biến, đích đến và cách sử dụng các yếu tố này khi tạo biểu đồ.

Đích đến

Kotlin DSL cung cấp cơ chế hỗ trợ được tích hợp sẵn cho ba loại đích đến: Fragment, Activity, và NavGraph. Mỗi đích đến này đều có chức năng tiện ích cùng dòng (inline) riêng để tạo và định cấu hình cho đích đến đó.

Đích đến của mảnh

Chiến lược phát hành đĩa đơn fragment() Hàm DSL có thể được tham số bằng lớp mảnh cho giao diện người dùng và loại tuyến dùng để nhận dạng duy nhất đích đến này, theo sau là một lambda nơi bạn có thể cung cấp cấu hình bổ sung như được mô tả trong phần Điều hướng với mục biểu đồ Kotlin DSL.

fragment<MyFragment, MyRoute> {
   label = getString(R.string.fragment_title)
   // custom argument types, deepLinks
}

Đích đến của hoạt động

Chiến lược phát hành đĩa đơn activity() Hàm DSL lấy một tham số loại cho tuyến đường nhưng không được tham số hoá thành bất kỳ lớp hoạt động triển khai nào. Thay vào đó, bạn sẽ đặt một activityClass (không bắt buộc) trong trailing lambda (lambda theo sau). Tính linh hoạt này cho phép bạn xác định đích đến của hoạt động cho một hoạt động phải được khởi chạy bằng cách sử dụng một giao diện ngầm ẩn ý định, trong đó lệnh gọi rõ ràng lớp hoạt động nào sẽ không hợp lý. Tương tự như với đích đến của mảnh, bạn cũng có thể định cấu hình nhãn, đối số tuỳ chỉnh và đường liên kết sâu.

activity<MyRoute> {
   label = getString(R.string.activity_title)
   // custom argument types, deepLinks...

   activityClass = MyActivity::class
}

Chiến lược phát hành đĩa đơn navigation() Hàm DSL có thể được dùng để tạo điều hướng lồng nhau biểu đồ. Hàm này nhận một loại cho tuyến đường để gán cho biểu đồ này. Hàm này cũng có 2 đối số: tuyến đường của đích đến bắt đầu trên biểu đồ và một lambda để định cấu hình biểu đồ. Phần tử hợp lệ bao gồm các đích đến khác, đối số tuỳ chỉnh loại, liên kết sâu và nhãn mô tả cho đích. Nhãn này có thể hữu ích cho việc liên kết biểu đồ điều hướng với các thành phần giao diện người dùng bằng cách sử dụng NavigationUI.

@Serializable data object HomeGraph
@Serializable data object Home

navigation<HomeGraph>(startDestination = Home) {
   // label, other destinations, deep links
}

Hỗ trợ đích đến tuỳ chỉnh

Nếu bạn đang sử dụng một loại đích đến mới không hỗ trợ trực tiếp Kotlin DSL, bạn có thể thêm các đích đến này vào DSL Kotlin bằng cách sử dụng addDestination():

// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}
addDestination(customDestination)

Ngoài ra, bạn cũng có thể sử dụng toán tử cộng một ngôi (unary plus operator) để thêm trực tiếp điểm đến mới được tạo vào biểu đồ:

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

Cung cấp các đối số cho đích đến

Đối số đích có thể được định nghĩa như một phần của lớp định tuyến. Đây có thể là xác định theo cách tương tự như đối với mọi lớp Kotlin. Đối số bắt buộc là được khai báo là các loại không thể có giá trị rỗng và đối số không bắt buộc được định nghĩa theo giá trị mặc định giá trị.

Cơ chế cơ bản để biểu thị các tuyến và đối số của chúng là chuỗi dựa trên cơ sở. Việc sử dụng chuỗi để lập mô hình các tuyến đường cho phép lưu trữ trạng thái điều hướng và khôi phục từ ổ đĩa trong quá trình định cấu hình các thay đổiquy trình do hệ thống khởi tạo tử. Vì lý do này, mỗi đối số điều hướng cần phải được chuyển đổi tuần tự, tức là đối số đó phải có phương thức chuyển đổi cách biểu diễn trong bộ nhớ của giá trị đối số thành String.

Chuyển đổi tuần tự Kotlin trình bổ trợ tự động tạo các phương thức chuyển đổi tuần tự cho cơ bản khi Chú giải @Serializable được thêm vào một đối tượng.

@Serializable
data class MyRoute(
  val id: String,
  val myList: List<Int>,
  val optionalArg: String? = null
)

fragment<MyFragment, MyRoute>

Cung cấp các kiểu tuỳ chỉnh

Đối với loại đối số tuỳ chỉnh, bạn cần cung cấp một lớp NavType tuỳ chỉnh. Chiến dịch này cho phép bạn kiểm soát chính xác cách phân tích cú pháp loại thông qua một tuyến hoặc đường liên kết sâu.

Ví dụ: một tuyến dùng để xác định màn hình tìm kiếm có thể chứa một lớp biểu thị các thông số tìm kiếm:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@Serializable
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

NavType tuỳ chỉnh có thể được viết thành:

val SearchParametersType = object : NavType<SearchParameters>(
  isNullableAllowed = false
) {
  override fun put(bundle: Bundle, key: String, value: SearchParameters) {
    bundle.putParcelable(key, value)
  }
  override fun get(bundle: Bundle, key: String): SearchParameters {
    return bundle.getParcelable(key) as SearchParameters
  }

  override fun serializeAsValue(value: SearchParameters): String {
    // Serialized values must always be Uri encoded
    return Uri.encode(Json.encodeToString(value))
  }

  override fun parseValue(value: String): SearchParameters {
    // Navigation takes care of decoding the string
    // before passing it to parseValue()
    return Json.decodeFromString<SearchParameters>(value)
  }
}

Sau đó, bạn có thể dùng kiểu tuỳ chỉnh này trong Kotlin DSL như bất kỳ kiểu nào khác:

fragment<SearchFragment, SearchRoute> {
    label = getString(R.string.plant_search_title)
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}

Khi điều hướng tới đích đến, hãy tạo một thực thể của tuyến đường:

val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))

Tham số này có thể lấy từ tuyến trong đích đến:

val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters

Liên kết sâu

Bạn có thể thêm liên kết sâu vào bất kỳ đích đến nào, giống như cách thêm liên kết sâu vào biểu đồ điều hướng bằng XML. Tất cả quy trình tương tự được xác định trong bài viết Tạo đường liên kết sâu cho một đích đến sẽ áp dụng cho quy trình bằng cách sử dụng Kotlin DSL.

Khi tạo đường liên kết sâu ngầm ẩn tuy nhiên, bạn lại không có tài nguyên điều hướng XML để có thể phân tích Phần tử <deepLink>. Do đó, bạn không thể dựa vào việc đặt <nav-graph> trong tệp AndroidManifest.xml và phải thêm ý định thay thế vào hoạt động của mình theo cách thủ công. Ý định bộ lọc mà bạn cung cấp phải khớp với đường dẫn cơ sở, hành động và mimetype của liên kết sâu của ứng dụng.

Đường liên kết sâu được thêm vào một đích đến bằng cách gọi hàm deepLink bên trong lambda của đích đến. Phương thức này chấp nhận tuyến dưới dạng một loại có tham số và tham số basePath cho đường dẫn cơ sở của URL dùng cho đường liên kết sâu.

Bạn cũng có thể thêm hành động và mimetype bằng cách sử dụng deepLinkBuilder trailing lambda.

Ví dụ sau đây sẽ tạo một URI liên kết sâu cho đích đến Home.

@Serializable data object Home

fragment<HomeFragment, Home>{
  deepLink<Home>(basePath = "www.example.com/home"){
    // Optionally, specify the action and/or mime type that this destination
    // supports
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
  }
}

Định dạng URI

Định dạng URI liên kết sâu được tạo tự động từ các trường của tuyến bằng cách sử dụng các quy tắc sau:

  • Các tham số bắt buộc được nối thêm dưới dạng thông số đường dẫn (ví dụ: /{id})
  • Các thông số có giá trị mặc định (thông số không bắt buộc) sẽ được thêm vào dưới dạng truy vấn tham số (ví dụ: ?name={name})
  • Bộ sưu tập được thêm vào dưới dạng tham số truy vấn (ví dụ: ?items={value1}&items={value2})
  • Thứ tự của các tham số khớp với thứ tự của các trường trong tuyến đường

Ví dụ: loại tuyến đường sau đây:

@Serializable data class PlantDetail(
  val id: String,
  val name: String,
  val colors: List<String>,
  val latinName: String? = null,
)

có định dạng URI được tạo là:

basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}

Không có giới hạn về số lượng liên kết sâu có thể thêm. Mỗi lần gọi deepLink(), một liên kết sâu mới sẽ được bổ sung vào một danh sách được duy trì cho đích đến đó.

Các điểm hạn chế

Trình bổ trợ Ang Args không tương thích với Kotlin DSL vì trình bổ trợ này tìm các tệp tài nguyên XML để tạo các lớp DirectionsArguments.