Fragmente und Kotlin DSL

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

Abhängigkeiten

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

Cool

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

Diagramm erstellen

Hier ist ein einfaches Beispiel, das auf der Vorlage Sonnenblume 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 den Navigationsdiagramm Ihrer App erstellen können, müssen Sie einen Ort zum Hosten der Diagramm. 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 nicht als Ressource definiert, 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 eines die Route berechnen. Das bedeutet, dass bei Verwendung von DSL kein Konzept für Aktionen vorliegt.

Im nächsten Schritt definieren Sie die Routen, die Sie bei der Definition Ihrer Diagramm.

Routen für die Grafik erstellen

XML-basierte Navigationsdiagramme werden als Teil geparst. des Android-Build-Prozesses. Für jede id wird eine numerische Konstante erstellt. das in der Grafik definierte Attribut enthält. Diese zur Build-Zeit generierten statischen IDs sind beim Erstellen des Navigationsdiagramms zur Laufzeit verfügbar, damit die Navigations-DSL verwendet serialisierbare anstelle von IDs. Jede Route wird durch einen eindeutigen Typ dargestellt.

Beim Umgang mit Argumenten sind diese in die Route integriert Typ aus. So können Sie mit der für Ihre Navigationsargumente.

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

Nachdem Sie Ihre Routen definiert haben, können Sie das Navigationsdiagramm erstellen.

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

In diesem Beispiel werden zwei Fragmentziele mithilfe der fragment() DSL-Builder-Funktion Für diese Funktion sind zwei Typen Argumente .

Erstens eine Fragment-Klasse der die UI für dieses Ziel bereitstellt. Diese Einstellung hat denselben Effekt wie Festlegen des Attributs android:name für definierte Fragmentziele mithilfe von XML.

Dann die Route. Dies muss ein serialisierbarer Typ sein, der von Any erweitert wird. Es alle Navigationsargumente enthalten, die von diesem Ziel verwendet werden, und ihre Typen.

Die Funktion akzeptiert auch eine optionale Lambda-Funktion für die zusätzliche Konfiguration, z. B. als Ziellabel sowie eingebettete Builder-Funktionen für benutzerdefinierte und Deeplinks.

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

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

In PlantDetailFragment können Sie die Navigationsargumente abrufen, indem Sie Folgendes abrufen: die aktuelle NavBackStackEntry und Anrufe toRoute um die Routeninstanz abzurufen.

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

Wenn PlantDetailFragment einen ViewModel verwendet, rufen Sie die Routeninstanz mithilfe von SavedStateHandle.toRoute

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

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 mit der Fragmentklasse für die Benutzeroberfläche und dem Routentyp, mit dem dieses Ziel eindeutig identifiziert wird, gefolgt von einer Lambda-Funktion Hier können Sie zusätzliche Konfigurationen vornehmen, wie unter Navigation durch die Kotlin-DSL-Grafik.

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

Ziel der Aktivität

Die activity() DSL-Funktion verwendet einen Typparameter für die Route, ist jedoch nicht parametrisiert. Implementierungs-Aktivitätsklasse. Stattdessen legen Sie eine optionale activityClass in ein nachgestelltes Lambda. Dank dieser Flexibilität können Sie ein Aktivitätsziel eine Aktivität, die mit einer impliziten Intent, wobei eine explizite macht keinen Sinn. Wie bei Fragmentzielen können Sie auch ein Label, benutzerdefinierte Argumente und Deeplinks konfigurieren.

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

   activityClass = MyActivity::class
}

Die navigation() Mit der DSL-Funktion kannst du eine verschachtelte Navigation erstellen. Graph Diese Funktion verwendet einen Typ -Parameter für die Route, die dieser Grafik zugewiesen werden soll. Außerdem sind zwei Argumente erforderlich: die Route des Ausgangsorts des Diagramms und eine Lambda-Funktion zum um das Diagramm zu konfigurieren. Gültige Elemente sind andere Ziele, benutzerdefiniertes Argument Typen, Deeplinks und ein beschreibendes Label für die Ziel. Dieses Label kann nützlich sein, um das Navigationsdiagramm mithilfe von NavigationUI

@Serializable data object HomeGraph
@Serializable data object Home

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

Unterstützung benutzerdefinierter Ziele

Wenn Sie einen neuen Zieltyp verwenden das Kotlin DSL nicht direkt unterstützt, können Sie diese Ziele Kotlin 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

Zielargumente können als Teil der Routenklasse definiert werden. Diese können wie jede andere Kotlin-Klasse definiert. Erforderliche Argumente sind als Typen definiert, die keine Nullwerte zulassen können, und optionale Argumente mit Werte.

Der zugrunde liegende Mechanismus zum Darstellen von Routen und ihren Argumenten ist ein String basiert. Durch die Verwendung von Strings zum Modellieren von Routen kann der Navigationsstatus gespeichert und während der Konfiguration vom Laufwerk wiederhergestellt Änderungen und vom System initiierter Prozess Tod. Aus diesem Grund Jedes Navigationsargument muss serialisierbar sein, das heißt, es sollte -Methode, die die speicherinterne Darstellung des Argumentwerts in einen String

Die Kotlin-Serialisierung Plug-in generiert automatisch Serialisierungsmethoden für grundlegende , wenn der Parameter Die Anmerkung @Serializable wird einem Objekt hinzugefügt.

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

fragment<MyFragment, MyRoute>

Benutzerdefinierte Typen bereitstellen

Für benutzerdefinierte Argumenttypen müssen Sie eine benutzerdefinierte NavType-Klasse angeben. Dieses lässt sich genau steuern, wie Ihr Typ von einer Route oder einem Deeplink geparst wird.

Beispielsweise könnte eine Route, die zur Definition eines Suchbildschirms verwendet wird, eine Klasse enthalten, die steht für die Suchparameter:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@Serializable
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, SearchRoute> {
    label = getString(R.string.plant_search_title)
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}

Erstellen Sie während der Navigation zum Ziel eine Instanz Ihrer Route:

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

Der Parameter kann aus der Route im Ziel abgerufen werden:

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

Deeplinks

Deeplinks können jedem Ziel hinzugefügt werden, genau wie bei einem XML-gestützten Navigationsdiagramm. Die unter Deeplink erstellen für ein Ziel auf den Prozess angewendet. wie Sie mit Kotlin DSL einen Deeplink erstellen.

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 der Datei AndroidManifest.xml und muss stattdessen intent hinzufügen Filter manuell zu Ihren Aktivitäten hinzufügen. Die Absicht Filter, den Sie angeben, sollten mit dem Basispfad, der Aktion und dem MIME-Typ für die Deeplinks Ihrer App.

Zum Hinzufügen von Deeplinks zu einem Ziel wird die darin enthaltene Funktion deepLink aufgerufen die Lambda-Funktion des Ziels. Sie akzeptiert die Route als parametrisierten Typ und einen basePath für den Basispfad der für den Deeplink verwendeten URL.

Sie können auch eine Aktion und einen MIME-Typ hinzufügen, indem Sie die deepLinkBuilder hinteren Lambda.

Im folgenden Beispiel wird ein Deeplink-URI für das Ziel Home erstellt.

@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/*"
  }
}

URI-Format

Das Deeplink-URI-Format wird automatisch aus den Feldern der Route generiert mithilfe der folgenden Regeln:

  • Erforderliche Parameter werden als Pfadparameter angehängt. Beispiel: /{id}
  • Parameter mit einem Standardwert (optionale Parameter) werden als Abfrage Parameter (Beispiel: ?name={name})
  • Sammlungen werden als Abfrageparameter angehängt. Beispiel: ?items={value1}&items={value2})
  • Die Reihenfolge der Parameter entspricht der Reihenfolge der Felder in der Route

Der folgende Routentyp:

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

hat folgendes URI-Format:

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

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.

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.