Programmatisch mit Kotlin-DSL ein Diagramm erstellen

Die Navigationskomponente bietet eine Kotlin-basierte domainspezifische Sprache oder DSL, das auf den Kotlin-Standards typsichere Builder. Mit dieser API können Sie Ihre Grafik deklarativ in Ihrem Kotlin-Code erstellen, und nicht in einer XML-Ressource. Dies kann nützlich sein, wenn Sie die Navigation Ihrer App dynamisch gestalten. Ihre App könnte beispielsweise eine Navigationskonfiguration von einem externen Webdienst im Cache um dynamisch ein Navigationsdiagramm im onCreate().

Abhängigkeiten

Fügen Sie zur Verwendung von Kotlin DSL die folgende Abhängigkeit zum build.gradle-Datei:

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 Sunflower App. In diesem Fall Beispiel mit zwei Zielen: home und plant_detail. Das home Ziel ist vorhanden, wenn der Nutzer die App zum ersten Mal startet. Dieses Ziel zeigt eine Liste der Pflanzen aus dem Garten der Nutzenden an. Wenn der Nutzer eine der navigiert die App zum Ziel plant_detail.

Abbildung 1 zeigt diese Ziele zusammen mit den für den Parameter plant_detail-Ziel und die von der App verwendete Aktion to_plant_detail um von home nach plant_detail zu gelangen.

<ph type="x-smartling-placeholder">
</ph> Die Sunflower-App hat zwei Ziele sowie eine Aktion, die
            verbindet.
Abbildung 1: Die Sunflower-App hat zwei Ziele: home und plant_detail sowie eine Aktion, die verbindet.

Kotlin-DSL-Navigationsdiagramm hosten

Bevor Sie das Navigationsdiagramm für Ihre App erstellen können, benötigen Sie einen Host, in der Grafik. Da in diesem Beispiel Fragmente verwendet werden, wird die Grafik NavHostFragment innerhalb einer 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>

Beachten Sie, dass das Attribut app:navGraph in diesem Beispiel nicht festgelegt ist. Das Diagramm ist nicht definiert als resource im Feld res/navigation-Ordner, daher muss er als Teil des onCreate() festgelegt werden in der Aktivität.

In XML verknüpft eine Aktion eine Ziel-ID mit einem oder mehreren Argumenten. Bei Verwendung des Navigations-DSL kann eine Route jedoch Argumente als Teil der Route. Das bedeutet, dass bei Verwendung von DSL kein Konzept für Aktionen vorliegt.

Im nächsten Schritt definieren Sie einige Konstanten, in der Grafik.

Konstanten für die Grafik erstellen

XML-basierte Navigationsdiagramme im Rahmen des Android-Build-Prozesses geparst. Eine numerische Konstante wird für jedes in der Grafik definierte id-Attribut. Diese zur Build-Zeit generierten statischen IDs sind beim Erstellen des Navigationsdiagramms während der Laufzeit nicht verfügbar. Navigations-DSL verwendet Routenstrings anstelle von IDs. Jede Route wird durch eine eindeutige Zeichenfolge. Es empfiehlt sich, diese als Konstanten zu definieren, Fehler aufgrund von Tippfehlern.

Beim Umgang mit Argumenten sind dies im Routenstring integriert ist. Die Einbindung dieser Logik in die Route kann, wie gesagt, das Risiko Fehler aufgrund von Tippfehlern.

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

Nachdem Sie Ihre Konstanten definiert haben, können Sie die Navigation Diagramm.

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-Funktion zwei Fragmentziele mithilfe des fragment() DSL-Builder-Funktion Für diese Funktion ist ein Routenstring für das Ziel erforderlich. die sich aus den Konstanten ergibt. Die Funktion akzeptiert auch eine optionale Lambda für die zusätzliche Konfiguration, z. B. für das Ziellabel, eingebetteten Builder-Funktionen für Argumente und Deeplinks.

Die Klasse Fragment, die die UI jedes Ziels verwaltet, wird im Inneren als parametrisierter Typ übergeben. spitze Klammern (<>) Dies hat denselben Effekt wie das Festlegen von android:name bei Fragmentzielen, die mit XML definiert sind.

Schließlich können Sie mit der Standardmethode von home nach plant_detail navigieren. NavController.Navigate() Anrufe:

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

In PlantDetailFragment können Sie den Wert des Arguments so abrufen: im folgenden Beispiel:

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

Wie Sie beim Navigieren Argumente angeben, erfahren Sie in der Bereitstellen von Zielargumenten.

Im weiteren Verlauf dieses Leitfadens werden gängige Navigationsgrafikelemente, Ziele, und wie Sie diese beim Erstellen Ihrer Grafik verwenden.

Reiseziele

Kotlin DSL bietet integrierte Unterstützung für drei Zieltypen: Fragment-, Activity- und NavGraph-Ziele, von denen jedes ein eigenes Ziel hat Inline-Erweiterungsfunktion zum Erstellen und Konfigurieren der Ziel.

Fragmentziele

Die fragment() Die DSL-Funktion kann auf die implementierende Fragmentklasse parametrisiert werden und nimmt eine eindeutiger Routenstring, der diesem Ziel zugewiesen werden soll, gefolgt von einer Lambda-Funktion, wobei können Sie zusätzliche Konfigurationen vornehmen, wie in den Mit der Kotlin-DSL-Grafik navigieren .

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

Ziel der Aktivität

Die activity() Die DSL-Funktion verwendet eine eindeutige Routenzeichenfolge, die diesem Ziel zugewiesen wird, nicht parametrisiert für eine Implementierungs-Aktivitätsklasse. Stattdessen legen Sie eine optionales activityClass in einer nachfolgenden Lambda-Funktion. Dank dieser Flexibilität können Sie ein Aktivitätsziel für eine Aktivität definieren, die mit einem impliziter Intent, wobei ein wäre keine explizite Aktivität. 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
}

Die navigation() Mit der DSL-Funktion lässt sich verschachteltes Navigationsdiagramm. Diese Funktion verwendet drei Argumente: eine Route zu der Grafik die Route des Startziels der Grafik Lambda zur weiteren Konfiguration des Graphen. Gültige Elemente sind andere Ziele, Argumente, Deeplinks und eine beschreibendes Label für das Ziel. Dieses Label kann nützlich sein, um das Navigationsdiagramm an die Benutzeroberfläche zu binden Komponenten mithilfe von Navigations-UI

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

Unterstützung benutzerdefinierter Ziele

Wenn Sie eine neuen Zieltyp, der keine Kotlin DSL direkt unterstützen, können Sie diese Ziele Ihrem Kotlin-Code DSL mit addDestination():

// 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 eine neue erstelltes Ziel direkt in die Grafik ein:

// 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 mithilfe der argument() auf NavDestinationBuilder, der Basisklasse für alle Ziel-Builder-Typen. Diese Funktion verwendet den Namen des Arguments als String und eine Lambda-Funktion, die zum Erstellen und Konfigurieren eines NavArgument

Innerhalb der Lambda-Funktion können Sie den Datentyp des Arguments angeben. Dies ist ein Standardwert, wenn zutreffend sind und 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 eine defaultValue angegeben ist, kann der Typ abgeleitet werden. Wenn sowohl ein defaultValue und ein type angegeben wird, müssen die Typen übereinstimmen. Weitere Informationen finden Sie in der NavType-Referenzdokumentation für ein vollständige Liste der verfügbaren Argumenttypen.

Benutzerdefinierte Typen bereitstellen

Bestimmte Typen wie ParcelableType und SerializableType, das Parsen von Werten aus Zeichenfolgen, die von Routen oder Deeplinks verwendet werden, nicht unterstützt. Das liegt daran, dass sie nicht auf Reflexion zur Laufzeit basieren. Durch die Bereitstellung einer benutzerdefinierten NavType können Sie genau steuern, wie Ihr Typ geparst wird. Deeplink zu aktivieren. So können Sie die Kotlin-Serialisierung oder andere Bibliotheken für reflexionsfreie Codierung und Decodierung Ihres benutzerdefinierten Typs bereitstellen.

Beispiel: Eine Datenklasse, die Suchparameter darstellt, die an Ihre Suchbildschirm könnte sowohl Serializable implementieren (zur Bereitstellung der Unterstützung für Codierung/Decodierung) und Parcelize (für Speichern und Wiederherstellen) von einem Bundle):

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

Eine benutzerdefinierte NavType könnte folgendermaßen 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 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)
  }
}

Dieser kann dann wie jeder andere Typ in Kotlin-DSL 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())
    }
}

NavType kapselt sowohl das Schreiben als auch das Lesen jedes Felds ein, was bedeutet, dass die NavType auch verwendet werden muss, wenn Sie zum Ziel, um sicherzustellen, dass die Formate übereinstimmen:

val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(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 einem XML-gestützten Navigationsdiagramm. Alle Verfahren, die in Deeplink für ein Ziel erstellen auf den Prozess der Erstellung expliziten Deeplinks mithilfe der Methode Kotlin DSL

Beim Erstellen eines impliziten Deeplinks Sie haben jedoch keine XML-Navigationsressource, die analysiert werden kann, <deepLink>-Elemente. Daher können Sie sich nicht darauf verlassen, <nav-graph> -Element in Ihrer AndroidManifest.xml-Datei und muss stattdessen Intent-Filter für Ihre Aktivitäten manuell Der von Ihnen bereitgestellte Intent-Filter sollte mit dem Basis-URL-Muster, der Aktion und MIME-Typ der Deeplinks Ihrer App.

Sie können für jeden einzelnen Deeplink eine spezifischere deeplink angeben Ziel mithilfe der deepLink() DSL-Funktion. Diese Funktion akzeptiert eine NavDeepLink, die einen String enthält für das URI-Muster, ein String für die Intent-Aktionen und ein String, die für den mimeType steht .

Beispiel:

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

Sie können beliebig viele Deeplinks hinzufügen. Bei jedem Anruf deepLink() wird ein neuer Deeplink an eine Liste angehängt, die für dieses Ziel verwaltet wird.

Ein komplexeres implizites Deeplink-Szenario, das auch Pfad und abfragebasierten Parametern:

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

Sie können Stringinterpolation um die Definition zu vereinfachen.

Beschränkungen

Das Plug-in Sichere Args ist nicht mit Kotlin DSL kompatibel, da das Plug-in nach XML-Ressourcendateien sucht, Generieren der Klassen Directions und Arguments.

Weitere Informationen

Sicherheit der Navigationstypen um zu erfahren, wie Sie Typsicherheit für Ihre Kotlin-DSL-Datei und Navigation Compose-Code.