Zbuduj wykres programowo za pomocą DSL Kotlin

Komponent Nawigacja udostępnia język oparty na domenie Kotlin. DSL, który opiera się na kodzie Kotlina kreatorów bezpiecznych typu. Ten interfejs API umożliwia deklaratycyjne skomponowanie grafu w kodzie Kotlin, a nie wewnątrz zasobu XML. Może to być przydatne, jeśli chcesz tworzyć do dynamicznego nawigacji w aplikacji. Na przykład aplikacja może pobierać oraz zapisać w pamięci podręcznej konfigurację nawigacji z zewnętrznej usługi sieciowej, a następnie użyć aby dynamicznie tworzyć wykres nawigacji w onCreate().

Zależności

Aby używać DSL Kotlin, dodaj następującą zależność do Plik build.gradle:

Odlotowe

dependencies {
    def nav_version = "2.7.7"

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

Kotlin

dependencies {
    val nav_version = "2.7.7"

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

Tworzenie wykresu

Zacznijmy od podstawowego przykładu opartego na Aplikacja Słonecznik Do tego celu mamy na przykład dwa miejsca docelowe: home i plant_detail. home miejsce docelowe jest obecne przy pierwszym uruchomieniu aplikacji przez użytkownika. To miejsce docelowe wyświetla listę roślin z ogrodu użytkownika. Gdy użytkownik wybierze jedną z tych opcji: rośliny, aplikacja przejdzie do miejsca docelowego plant_detail.

Rys. 1 przedstawia te miejsca docelowe wraz z argumentami wymaganymi przez miejsce docelowe plant_detail i działanie to_plant_detail używane przez aplikację aby przejść z home do plant_detail.

Aplikacja Sunflower ma 2 miejsca docelowe wraz z działaniem, które
            ich łączy.
Rysunek 1. Aplikacja Sunflower ma 2 miejsca docelowe: home i plant_detail oraz działanie, które ich łącząca.

Hosting grafu nawigacyjnego DSL Kotlin

Aby utworzyć wykres nawigacyjny aplikacji, musisz mieć miejsce do przechowywania wykres. W tym przykładzie użyto fragmentów, więc wykres jest przechowywany w NavHostFragment wewnątrz FragmentContainerView:

<!-- 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>

Zwróć uwagę, że w tym przykładzie atrybut app:navGraph nie jest ustawiony. Wykres nie jest zdefiniowany jako zasobu w res/navigation, więc należy ustawić go jako część folderu onCreate() w trakcie aktywności.

Działanie w pliku XML wiąże identyfikator miejsca docelowego z co najmniej jednym argumentem. Jednak w przypadku korzystania z nawigacji DSL trasa może zawierać argumenty drogi. Oznacza to, że przy korzystaniu z DSL nie ma koncepcji działań.

Następnym krokiem jest określenie kilku stałych, których użyjesz do określenia wykres.

Utwórz stałe do wykresu

Wykresy nawigacyjne oparte na formacie XML są analizowane w ramach procesu kompilacji Androida. Zostanie utworzona stała liczbowa dla każdego atrybutu id zdefiniowanego na wykresie. Te statyczne wartości wygenerowane podczas kompilacji Identyfikatory nie są dostępne podczas tworzenia wykresu nawigacji w czasie działania, więc Nawigacja DSL używa ciągów tras zamiast identyfikatorów. Każda trasa jest reprezentowana przez unikalny ciąg znaków. Warto zdefiniować je jako stałe, aby zmniejszać ryzyko popełnienia literówek.

Gdy chodzi o argumenty, są to wbudowanej w ciąg znaków trasy. Wbudowanie takiej logiki w trasę może ponownie zmniejszyć ryzyko z błędami literowymi.

object nav_routes {
    const val home = "home"
    const val plant_detail = "plant_detail"
}

object nav_arguments {
    const val plant_id = "plant_id"
    const val plant_name = "plant_name"
}

Po zdefiniowaniu stałych możesz utworzyć nawigację wykres.

val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
    startDestination = nav_routes.home
) {
    fragment<HomeFragment>(nav_routes.home) {
        label = resources.getString(R.string.home_title)
    }

    fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
        label = resources.getString(R.string.plant_detail_title)
        argument(nav_arguments.plant_id) {
            type = NavType.StringType
        }
    }
}

W tym przykładzie na końcu funkcja lambda określa 2 miejsca docelowe fragmentów za pomocą parametru fragment() Funkcja konstruktora DSL. Ta funkcja wymaga ciągu trasy dla miejsca docelowego uzyskane ze stałych. Funkcja akceptuje też opcjonalne wartości lambda na potrzeby dodatkowej konfiguracji, np. etykiety miejsca docelowego, z wbudowanymi funkcjami kreatora na potrzeby argumentów i precyzyjnych linków.

Zajęcia Fragment, które zarządza interfejsem każdego miejsca docelowego jest przekazywany jako typ z parametrami nawiasy kątowe (<>). Efekt jest taki sam jak w przypadku ustawienia android:name w miejscach docelowych fragmentów zdefiniowanych za pomocą kodu XML.

Możesz też nawigować z home do plant_detail, korzystając ze standardowej trasy NavController.navigation() połączenia:

private fun navigateToPlant(plantId: String) {
   findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}

W funkcji PlantDetailFragment możesz uzyskać podaną wartość argumentu w tym przykładzie:

val plantId: String? = arguments?.getString(nav_arguments.plant_id)

Szczegółowe informacje na temat podawania argumentów podczas nawigacji można znaleźć w podawanie argumentów miejsca docelowego.

W pozostałej części tego przewodnika znajdziesz opis często używanych elementów wykresu nawigacji, miejsc docelowych, i jak ich używać do tworzenia wykresów.

Miejsca docelowe

Kotlin DSL ma wbudowaną obsługę trzech typów miejsc docelowych: Fragment, Activity i NavGraph miejsca docelowe, z których każde ma własne wbudowana funkcja rozszerzenia dostępna do tworzenia i konfigurowania miejsce docelowe.

Miejsca docelowe fragmentów kodu

fragment() Funkcję DSL można dostosować do parametrów implementacji klasy fragmentu i pobierać unikalny ciąg trasy, który ma zostać przypisany do tego miejsca docelowego, po którym następuje znak lambda, gdzie możesz podać dodatkową konfigurację, zgodnie z Nawigacja przy użyciu wykresu DSL Kotlin .

fragment<FragmentDestination>(nav_routes.route_name) {
   label = getString(R.string.fragment_title)
   // arguments, deepLinks
}

Miejsce docelowe aktywności

activity() Funkcja DSL wykorzystuje unikalny ciąg trasy do przypisania do tego miejsca docelowego, ale jest nie ma parametru przypisanego do żadnej implementowanej klasy aktywności. Zamiast tego musisz ustawić opcjonalnie activityClass w parametrze lambda na końcu. Ta elastyczność umożliwia określ miejsce docelowe aktywności, która powinna zostać uruchomiona za pomocą intencji niejawnych, gdzie nie będą miały sensu. Podobnie jak w przypadku miejsc docelowych fragmentów, możesz skonfigurować etykietę, argumenty i precyzyjne linki.

activity(nav_routes.route_name) {
   label = getString(R.string.activity_title)
   // arguments, deepLinks...

   activityClass = ActivityDestination::class
}

navigation() Za pomocą funkcji DSL można utworzyć zagnieżdżony wykres nawigacyjny. Ta funkcja przyjmuje 3 argumenty: trasę do przypisz do wykresu, trasę początkowego miejsca docelowego wykresu oraz lambda w celu dalszego skonfigurowania wykresu. Prawidłowe elementy to m.in. inne miejsca docelowe, precyzyjne linki, etykietę opisową miejsca docelowego. Ta etykieta może być przydatna do powiązania grafu nawigacyjnego z interfejsem użytkownika komponenty korzystające z Nawigacja

navigation("route_to_this_graph", nav_routes.home) {
   // label, other destinations, deep links
}

Obsługa niestandardowych miejsc docelowych

Jeśli używasz tagu nowy typ miejsca docelowego, który nie obsługuje bezpośrednio konsolę DSL Kotlin, możesz dodać te miejsca docelowe do DSL z addDestination():

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

Możesz też użyć jednoargumentowego operatora plus, aby dodać nowe miejsce docelowe bezpośrednio na wykres:

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

Podawanie argumentów miejsca docelowego

Każde miejsce docelowe może definiować argumenty opcjonalne lub wymagane. Działania można zdefiniować za pomocą argument() funkcja w funkcji NavDestinationBuilder, która jest klasą podstawową dla wszystkich typów kreatora miejsc docelowych. Ta funkcja przyjmuje nazwę argumentu jako ciąg znaków oraz funkcję lambda, która służy do tworzenia i konfigurowania NavArgument

W obiekcie lambda możesz określić typ danych argumentu. Wartość domyślną, jeśli ma zastosowanie i wskazuje, czy dopuszcza on wartość null.

fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
    label = getString(R.string.plant_details_title)
    argument(nav_arguments.plant_id) {
        type = NavType.StringType
        defaultValue = getString(R.string.default_plant_id)
        nullable = true  // default false
    }
}

Jeśli podano defaultValue, można wywnioskować typ. Jeśli zarówno defaultValue, i type, typy muszą być zgodne. Zobacz Dokumentacja NavType pełna lista dostępnych typów argumentów.

Podawanie typów niestandardowych

Niektóre typy danych, na przykład ParcelableType oraz SerializableType, nie obsługują analizowania wartości z ciągów znaków używanych przez trasy lub precyzyjne linki. Nie wymagają one odbicia w czasie działania. Poprzez dodanie pola niestandardowego NavType, możesz dokładnie określić, w jaki sposób Twój typ jest analizowany z trasy lub precyzyjny link. Dzięki temu możesz używać Serializacja Kotlin lub inna bibliotek do kodowania i dekodowania niestandardowego typu bez odbicia lustrzanego.

Na przykład klasa danych, która reprezentuje parametry wyszukiwania przekazywane do na ekranie wyszukiwania można zaimplementować zarówno Serializable (aby zapewnić obsługa kodowania/dekodowania) oraz Parcelize (aby obsługiwać zapisywanie i przywracanie danych z: Bundle):

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

Niestandardowy obiekt NavType może być zapisany w ten sposób:

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)
  }
}

Możesz go następnie użyć w DSL Kotlin tak jak dowolnego innego:

fragment<SearchFragment>(nav_routes.plant_search) {
    label = getString(R.string.plant_search_title)
    argument(nav_arguments.search_parameters) {
        type = SearchParametersType
        defaultValue = SearchParameters("cactus", emptyList())
    }
}

NavType obejmuje zapis i odczyt każdego pola, które oznacza, że musisz użyć NavType również przy przejściu do miejsce docelowe, aby zapewnić zgodność formatów:

val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")

Parametr ten można uzyskać z argumentów w miejscu docelowym:

val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)

Precyzyjne linki

Precyzyjne linki można dodawać do każdego miejsca docelowego, tak jak w przypadku witryny opartej na formacie XML. wykres nawigacyjny. Wszystkie te same procedury zdefiniowane w Tworzenie precyzyjnego linku do miejsca docelowego mają zastosowanie do procesu tworzenia precyzyjny link za pomocą funkcji Kotlin DSL

Podczas tworzenia niejawnego precyzyjnego linku nie masz jednak zasobu nawigacji XML, który można przeanalizować <deepLink> elementów. Dlatego nie można polegać na umieszczeniu <nav-graph> element w pliku AndroidManifest.xml i musi zamiast tego dodać element ręcznie za pomocą filtrów intencji do aktywności. Podany filtr intencji powinien pasować do wzorca podstawowego adresu URL, działania typu MIME precyzyjnych linków aplikacji.

Dla każdego pojedynczego precyzyjnego linku możesz podać bardziej szczegółowe deeplink miejsce docelowe za pomocą deepLink(). Funkcja DSL. Ta funkcja akceptuje żądania NavDeepLink zawierające String reprezentujący wzorzec identyfikatora URI, znacznik String reprezentujący działania intencji oraz tag String reprezentujący mimeType .

Na przykład:

deepLink {
    uriPattern = "http://www.example.com/plants/"
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
}

Nie ma ograniczeń co do liczby precyzyjnych linków, które możesz dodać. Za każdym razem, gdy do kogoś dzwonisz deepLink() nowy precyzyjny link zostanie dołączony do listy, która jest przechowywana w tym miejscu docelowym.

Bardziej skomplikowany scenariusz niejawnego precyzyjnego linku, który określa też ścieżkę parametry oparte na zapytaniach wyświetlają się poniżej:

val baseUri = "http://www.example.com/plants"

fragment<PlantDetailFragment>(nav_routes.plant_detail) {
   label = getString(R.string.plant_details_title)
   deepLink(navDeepLink {
    uriPattern = "${baseUri}/{id}"
   })
   deepLink(navDeepLink {
    uriPattern = "${baseUri}/{id}?name={plant_name}"
   })
}

Za pomocą interpolacja ciągów znaków aby uprościć definicję.

Ograniczenia

Wtyczka Safe Args niekompatybilny z DSL Kotlin, ponieważ wtyczka szuka plików zasobów XML w celu wygenerować zajęcia Directions i Arguments.

Więcej informacji

Zapoznaj się z informacjami na temat bezpieczeństwa typu nawigacji. znajdziesz więcej informacji o zapewnianiu bezpieczeństwa typu dla DSL Kotlin i Kod nawigacji.