Die Navigationskomponente bietet eine Kotlin-basierte domainspezifische Sprache (DSL), die auf den typsicheren Buildern von Kotlin basiert.
Mit dieser API können Sie Diagramme deklarativ in Ihrem Kotlin-Code anstatt in einer XML-Ressource erstellen. Dies kann nützlich sein, wenn Sie die Navigation Ihrer App dynamisch erstellen möchten. Ihre App könnte beispielsweise eine Navigationskonfiguration von einem externen Webdienst herunterladen, im Cache speichern und diese Konfiguration dann verwenden, um dynamisch ein Navigationsdiagramm in der Funktion onCreate()
Ihrer Aktivität zu erstellen.
Abhängigkeiten
Fügen Sie der Datei build.gradle
Ihrer App die folgende Abhängigkeit hinzu, um Kotlin-DSL zu verwenden:
Groovig
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") }
Diagramm erstellen
Beginnen wir mit einem einfachen Beispiel, das auf der Sunflower-Anwendung basiert. In diesem Beispiel gibt es zwei Ziele: home
und plant_detail
. Das Ziel home
ist vorhanden, wenn der Nutzer die App zum ersten Mal startet. An diesem Ziel wird eine Liste der Pflanzen aus dem Garten des Nutzers angezeigt. Wenn der Nutzer eine der Pflanzen auswählt, ruft die App das Ziel plant_detail
auf.
Abbildung 1 zeigt diese Ziele zusammen mit den Argumenten, die vom Ziel plant_detail
und der Aktion to_plant_detail
erforderlich sind, mit der die Anwendung von home
nach plant_detail
navigiert.
Kotlin-DSL-Navigationsdiagramm hosten
Bevor Sie das Navigationsdiagramm Ihrer Anwendung erstellen können, benötigen Sie einen Ort, an dem das Diagramm gehostet werden kann. In diesem Beispiel werden Fragmente verwendet und der Graph daher in einem NavHostFragment
innerhalb eines FragmentContainerView
gehostet:
<!-- 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>
Beachten Sie, dass das Attribut app:navGraph
in diesem Beispiel nicht festgelegt ist. Das Diagramm ist im Ordner res/navigation
nicht als Ressource definiert. Daher muss es als Teil des Prozesses onCreate()
in der Aktivität festgelegt werden.
In XML verbindet eine Aktion eine Ziel-ID mit einem oder mehreren Argumenten. Bei Verwendung der Navigations-DSL kann eine Route jedoch Argumente als Teil der Route enthalten. Das bedeutet, dass es bei der Verwendung von DSL keine Aktionen gibt.
Der nächste Schritt besteht darin, einige Konstanten zu definieren, die Sie beim Definieren der Grafik verwenden werden.
Konstanten für die Grafik erstellen
XML-basierte Navigationsdiagramme werden als Teil des Android-Build-Prozesses geparst. Für jedes im Diagramm definierte id
-Attribut wird eine numerische Konstante erstellt. Diese statischen, während der Build-Zeit generierten IDs sind nicht verfügbar, wenn das Navigationsdiagramm zur Laufzeit erstellt wird. Daher verwendet die Navigations-DSL Routenstrings anstelle von IDs. Jede Route wird durch einen eindeutigen String dargestellt. Es empfiehlt sich, diese als Konstanten zu definieren, um das Risiko von Fehlern im Zusammenhang mit Tippfehlern zu verringern.
Bei der Verarbeitung von Argumenten werden diese in den Routingstring integriert. Durch das Einbinden dieser Logik in die Route können Sie wiederum das Risiko verringern, dass sich Tippfehler einschleichen.
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"
}
Ein Diagramm mit der NavGraphBuilder-DSL erstellen
Nachdem Sie die Konstanten definiert haben, können Sie das Navigationsdiagramm erstellen.
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
}
}
}
In diesem Beispiel definiert die nachgestellte Lambda zwei Fragmentziele mithilfe der DSL-Builder-Funktion fragment()
. Diese Funktion benötigt einen Routenstring für das Ziel, der aus den Konstanten abgerufen wird. Die Funktion akzeptiert auch ein optionales Lambda für die zusätzliche Konfiguration, z. B. das Ziellabel, sowie eingebettete Builder-Funktionen für Argumente und Deeplinks.
Die Klasse Fragment
, die die UI jedes Ziels verwaltet, wird als parametrierter Typ in spitzen Klammern (<>
) übergeben. Dies hat denselben Effekt wie das Festlegen des Attributs android:name
bei Fragmentzielen, die über XML definiert werden.
Kotlin-DSL-Grafik verwenden
Schließlich können Sie mithilfe der standardmäßigen NavController.browse()-Aufrufe von home
zu plant_detail
wechseln:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
In PlantDetailFragment
können Sie den Wert des Arguments abrufen, wie im folgenden Beispiel gezeigt:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
Ausführliche Informationen zum Bereitstellen von Argumenten beim Navigieren finden Sie im Abschnitt Zielargumente bereitstellen.
Im weiteren Verlauf dieses Leitfadens werden gängige Elemente und Ziele von Navigationsdiagrammen beschrieben und es wird erläutert, wie Sie diese beim Erstellen des Diagramms verwenden können.
Ziele
Kotlin-DSL bietet integrierte Unterstützung für drei Zieltypen: Fragment
-, Activity
- und NavGraph
-Ziele, von denen jedes eine eigene Inline-Erweiterungsfunktion zum Erstellen und Konfigurieren des Ziels hat.
Fragmentziele
Die DSL-Funktion fragment()
kann für die implementierende Fragmentklasse parametrisiert werden und verwendet einen eindeutigen Routenstring, der diesem Ziel zugewiesen wird, gefolgt von einer Lambda-Funktion, in der Sie zusätzliche Konfigurationen bereitstellen können, wie im Abschnitt Mit der Kotlin-DSL-Grafik navigieren beschrieben.
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
Aktivitätsziel
Die DSL-Funktion activity()
verwendet einen eindeutigen Routenstring, um diesem Ziel zuzuweisen, ist aber nicht mit einer implementierenden Aktivitätsklasse parametriert. Stattdessen legst du einen optionalen activityClass
in einer nachgestellten Lambda-Funktion fest. Mit dieser Flexibilität können Sie ein Aktivitätsziel für eine Aktivität definieren, die mit einem impliziten Intent gestartet werden soll, wenn eine explizite Aktivitätsklasse nicht sinnvoll wäre. Wie bei Fragmentzielen können Sie auch ein Label, Argumente und Deeplinks konfigurieren.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
Ziel der Navigationsgrafik
Mit der DSL-Funktion navigation()
können Sie eine verschachtelte Navigationsgrafik erstellen.
Diese Funktion verwendet drei Argumente: eine Route, die der Grafik zugewiesen werden soll, die Route des Startziels der Grafik und ein Lambda zur weiteren Konfiguration des Diagramms. Zu den gültigen Elementen gehören andere Ziele, Argumente, Deeplinks und ein beschreibendes Label für das Ziel.
Dieses Label kann nützlich sein, um das Navigationsdiagramm über NavigationUI an UI-Komponenten zu binden.
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
Unterstützung benutzerdefinierter Ziele
Wenn Sie einen neuen Zieltyp verwenden, der Kotlin-DSL nicht direkt unterstützt, können Sie dem Kotlin-DSL die folgenden Ziele mit addDestination()
hinzufügen:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
Alternativ können Sie auch den unären Plus-Operator verwenden, um ein neu erstelltes Ziel direkt dem Diagramm hinzuzufügen:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
Zielargumente bereitstellen
Jedes Ziel kann Argumente definieren, die optional oder erforderlich sind. Aktionen können mit der Funktion argument()
in NavDestinationBuilder
definiert werden. Das ist die Basisklasse für alle Ziel-Builder-Typen. Diese Funktion verwendet den Namen des Arguments als String und eine Lambda-Funktion, mit der ein NavArgument
erstellt und konfiguriert wird.
Innerhalb der Lambda-Funktion können Sie den Datentyp des Arguments, einen Standardwert (falls anwendbar) und angeben, ob Nullwerte zulässig sind.
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
}
}
Wenn ein defaultValue
angegeben ist, kann der Typ abgeleitet werden. Wenn sowohl ein defaultValue
als auch ein type
angegeben ist, müssen die Typen übereinstimmen. Eine vollständige Liste der verfügbaren Argumenttypen finden Sie in der Referenzdokumentation zu NavType.
Benutzerdefinierte Typen bereitstellen
Bestimmte Typen wie ParcelableType
und SerializableType
unterstützen nicht das Parsen von Werten aus den Strings, die von Routen oder Deeplinks verwendet werden.
Das liegt daran, dass sie nicht von einer Reflexion zur Laufzeit abhängig sind. Durch Angabe einer benutzerdefinierten NavType
-Klasse können Sie genau steuern, wie Ihr Typ aus einer Route oder einem Deeplink geparst wird. So können Sie die Kotlin-Serialisierung oder andere Bibliotheken verwenden, um eine reflektierende Codierung und Decodierung für Ihren benutzerdefinierten Typ bereitzustellen.
Beispielsweise könnte eine Datenklasse, die Suchparameter darstellt, die an den Suchbildschirm übergeben werden, sowohl Serializable
(zur Unterstützung der Codierung/Decodierung) als auch Parcelize
(zur Unterstützung des Speicherns und Wiederherstellens aus einem Bundle
) implementieren:
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
Eine benutzerdefinierte NavType
könnte so geschrieben werden:
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 parseValue(value: String): SearchParameters {
return Json.decodeFromString<SearchParameters>(value)
}
// Only required when using Navigation 2.4.0-alpha07 and lower
override val name = "SearchParameters"
}
Dieser kann dann in Ihrer Kotlin-DSL wie jeder andere Typ verwendet werden:
fragment<SearchFragment>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
In diesem Beispiel wird die Kotlin-Serialisierung verwendet, um den Wert aus dem String zu parsen. Das bedeutet, dass die Kotlin-Serialisierung auch beim Aufrufen des Ziels verwendet werden muss, damit die Formate übereinstimmen:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = Uri.encode(Json.encodeToString(params))
navController.navigate("${nav_routes.plant_search}/$searchArgument")
Der Parameter kann aus den Argumenten im Ziel abgerufen werden:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
Deeplinks
Deeplinks können jedem Ziel hinzugefügt werden, genau wie bei einer XML-gestützten Navigationsgrafik. Die unter Deeplink für ein Ziel erstellen beschriebenen Verfahren gelten auch für das Erstellen eines expliziten Deeplinks mit Kotlin-DSL.
Beim Erstellen eines impliziten Deeplinks haben Sie jedoch keine XML-Navigationsressource, die auf <deepLink>
-Elemente analysiert werden kann. Daher können Sie sich nicht darauf verlassen, ein <nav-graph>
-Element in Ihre AndroidManifest.xml
-Datei einzufügen. Stattdessen müssen Sie Ihrer Aktivität manuell Intent-Filter hinzufügen.
Der von Ihnen angegebene Intent-Filter sollte dem Basis-URL-Muster, der Aktion und dem MIME-Typ der Deeplinks Ihrer App entsprechen.
Mit der DSL-Funktion deepLink()
können Sie für jedes einzelne Ziel mit Deeplink eine spezifischere deeplink
angeben. Diese Funktion akzeptiert einen NavDeepLink
, der einen String
für das URI-Muster, einen String
für die Intent-Aktionen und einen String
für den mimeType enthält .
Beispiele:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
Sie können beliebig viele Deeplinks hinzufügen. Jedes Mal, wenn Sie deepLink()
aufrufen, wird einer Liste, die für dieses Ziel gepflegt wird, ein neuer Deeplink angehängt.
Im Folgenden wird ein komplexeres Szenario mit impliziten Deeplinks dargestellt, in dem auch pfad- und abfragebasierte Parameter definiert werden:
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}"
})
}
Mit der Stringinterpolation können Sie die Definition vereinfachen.
Einschränkungen
Das Safe Args-Plug-in ist nicht mit Kotlin-DSL kompatibel, da das Plug-in nach XML-Ressourcendateien sucht, um die Klassen Directions
und Arguments
zu generieren.
Weitere Informationen
Auf der Seite Sicherheit für Navigationstypen erfahren Sie, wie Sie für Kotlin-DSL- und Navigation Compose-Code Sicherheit bieten.