Fragments et DSL Kotlin

Le composant Navigation fournit un langage spécifique à un domaine basé sur Kotlin. le DSL, qui repose sur la protection du typage de Kotlin constructeurs pour en savoir plus. Cette API vous permet de composer votre graphique de manière déclarative dans votre code Kotlin, que dans une ressource XML. Cela peut être utile si vous souhaitez créer la navigation de manière dynamique. Par exemple, votre application peut télécharger et mettre en cache la configuration de navigation à partir d'un service Web externe, pour créer dynamiquement un graphique de navigation dans le fonction onCreate().

Dépendances

Pour utiliser le DSL Kotlin avec des fragments, ajoutez la dépendance suivante au fichier Fichier 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")
}

Créer un graphe

Voici un exemple basique basé sur le modèle Tournesol l'application Nest. Pour cette Par exemple, nous avons deux destinations: home et plant_detail. La destination home est présente lorsque l'utilisateur lance l'application pour la première fois. Elle affiche la liste des plantes du jardin de l'utilisateur. Lorsque l'utilisateur sélectionne l'une des plantes, l'application accède à la destination plant_detail.

La figure 1 illustre ces destinations, ainsi que les arguments requis par la destination plant_detail et une action, to_plant_detail, que l'application utilise pour passer de home à plant_detail.

L'application Sunflower comprend deux destinations reliées par une action.
Figure 1 : L'application Sunflower comporte deux destinations, home et plant_detail, ainsi qu'une action qui les relie entre elles.

Héberger un graphe de navigation DSL Kotlin

Avant de pouvoir créer le graphique de navigation de votre application, vous avez besoin d'un emplacement pour héberger le graphique. Cet exemple utilise des fragments. Il héberge donc le graphe dans un élément NavHostFragment situé dans un objet 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>

Notez que l'attribut app:navGraph n'est pas défini dans cet exemple. Le graphique n'est pas défini comme une ressource le dossier res/navigation. Il doit donc être défini dans le cadre de onCreate() dans l'activité.

En XML, une action associe un ID de destination à un ou plusieurs arguments. Toutefois, lorsque vous utilisez le langage DSL de navigation, un itinéraire peut contenir des arguments la route. Autrement dit, il n'existe pas de concept d'action lorsque vous utilisez le langage DSL.

L'étape suivante consiste à définir les routes à utiliser lorsque vous définissez votre graphique.

Créer des itinéraires pour votre graphique

Les graphiques de navigation XML sont analysés en tant que parties du processus de compilation Android. Une constante numérique est créée pour chaque id défini dans le graphique. Ces ID statiques générés lors de la compilation ne sont pas disponible lorsque vous créez votre graphique de navigation au moment de l'exécution. Ainsi, le langage DSL de navigation utilise des valeurs sérialisables d'entraînement au lieu de ID. Chaque itinéraire est représenté par un type unique.

Dans le cas des arguments, ceux-ci sont intégrés à la route type. Vous bénéficiez ainsi de la sûreté du typage pour vos arguments de navigation.

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

Une fois que vous avez défini vos itinéraires, vous pouvez créer le graphique de navigation.

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

Dans cet exemple, deux destinations de fragment sont définies à l'aide du fragment() Fonction de compilateur DSL. Cette fonction requiert deux types arguments pour en savoir plus.

D'abord, une classe Fragment qui fournit l'UI pour cette destination. La définition de ce paramètre a le même effet que Définir l'attribut android:name sur les destinations de fragment définies au format XML.

Deuxièmement, l'itinéraire. Il doit s'agir d'un type sérialisable qui s'étend à partir de Any. Il doit contenir tous les arguments de navigation qui seront utilisés par cette destination, et leurs types.

La fonction accepte également un lambda facultatif pour une configuration supplémentaire, telle que comme libellé de destination, ainsi que des fonctions de compilateur intégrées pour des les arguments et les liens profonds.

Enfin, vous pouvez naviguer de home à plant_detail en utilisant NavController.navigate() appels:

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

Dans PlantDetailFragment, vous pouvez obtenir les arguments de navigation en obtenant l'actuel NavBackStackEntry et appel toRoute pour obtenir l'instance de route.

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

Si PlantDetailFragment utilise un ViewModel, obtenez l'instance de route à l'aide de la commande SavedStateHandle.toRoute

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

Le reste de ce guide décrit les éléments courants du graphe de navigation, les destinations et leur utilisation lors de la création du graphe.

Destinations

Le langage DSL Kotlin est compatible avec trois types de destinations : Fragment, Activity et NavGraph. Chacune d'elles dispose de sa propre fonction d'extension intégrée permettant sa compilation et sa configuration.

Destination "fragment"

La fragment() La fonction DSL peut être paramétrée avec la classe de fragment pour l'interface utilisateur et la fonction type d'itinéraire utilisé pour identifier cette destination de manière unique, suivi d'un lambda où vous pouvez fournir une configuration supplémentaire, comme décrit dans la section Navigation avec votre graphique DSL Kotlin.

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

Destination "activity"

La activity() La fonction DSL utilise un paramètre de type pour la route, mais n'est pas paramétrée pour toute implémentation de classe d'activité. À la place, vous définissez un activityClass facultatif dans un lambda de fin. Cette flexibilité vous permet de définir une destination d'activité une activité qui doit être lancée à l'aide d'une commande implicite un intent, lorsqu'un élément explicite la classe d'activité n'aurait aucun sens. Comme pour les destinations de fragment, vous pouvez également configurer un libellé, des arguments personnalisés et des liens profonds.

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

   activityClass = MyActivity::class
}

La navigation() La fonction DSL peut être utilisée pour créer une navigation imbriquée. graphique. Cette fonction accepte un type pour l'itinéraire à attribuer à ce graphique. Elle utilise également deux arguments: la route de la destination de départ du graphe et un lambda pour aller plus loin configurer le graphe. Les éléments valides incluent les autres destinations, les arguments personnalisés de liens profonds, ainsi qu'une étiquette descriptive destination. Cette étiquette peut être utile pour lier le graphique de navigation aux composants d'interface utilisateur à l'aide de NavigationUI

@Serializable data object HomeGraph
@Serializable data object Home

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

Destinations personnalisées compatibles

Si vous utilisez un nouveau type de destination qui n'est pas directement compatible avec le DSL Kotlin, vous pouvez ajouter ces destinations votre DSL Kotlin à l'aide de addDestination():

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

Vous pouvez également utiliser l'opérateur unaire plus (+) pour ajouter une destination que vous venez de créer directement au graphe :

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

Fournir des arguments de destination

Les arguments de destination peuvent être définis dans la classe d'itinéraire. Il peut s'agir de la même manière que pour n'importe quelle classe Kotlin. Les arguments obligatoires sont définis comme des types ne pouvant pas avoir une valeur nulle, et les arguments facultatifs sont définis avec valeurs.

Le mécanisme sous-jacent pour représenter les routes et leurs arguments est une chaîne à vos clients. L'utilisation de chaînes pour modéliser les routes permet de stocker l'état de navigation et à partir du disque lors de la configuration modifications et le processus initié par le système la mort. Pour cette raison, chaque argument de navigation doit être sérialisable, c'est-à-dire qu'il doit avoir une qui convertit la représentation en mémoire de la valeur d'argument en String

La sérialisation Kotlin plug-in génère automatiquement des méthodes de sérialisation pour les modèles de base lorsque L'annotation @Serializable est ajoutée à un objet.

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

fragment<MyFragment, MyRoute>

Fournir des types personnalisés

Pour les types d'arguments personnalisés, vous devez fournir une classe NavType personnalisée. Ce vous permet de contrôler exactement la façon dont votre type est analysé à partir d'un itinéraire ou d'un lien profond.

Par exemple, un itinéraire utilisé pour définir un écran de recherche peut contenir une classe qui représente les paramètres de recherche:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

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

Un élément NavType personnalisé peut être écrit comme suit :

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

Vous pouvez ensuite l'utiliser dans le langage DSL Kotlin comme n'importe quel autre type :

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

Lorsque vous accédez à la destination, créez une instance de votre itinéraire:

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

Le paramètre peut être obtenu à partir de l'itinéraire dans la destination:

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

Liens profonds

Des liens profonds peuvent être ajoutés à n'importe quelle destination, tout comme avec un graphe de navigation basé sur XML. Toutes les procédures définies dans la section Créer un lien profond pour une destination s'appliquent au processus de la création d'un lien profond à l'aide du langage DSL Kotlin.

Lorsque vous créez un lien profond implicite Toutefois, vous ne disposez d'aucune ressource de navigation XML pouvant être analysée <deepLink>. Par conséquent, vous ne pouvez pas compter sur le placement d'un <nav-graph>. dans votre fichier AndroidManifest.xml et devez ajouter à la place l'intent des filtres à votre activité. L'intention que vous fournissez doit correspondre au chemin d'accès de base, à l'action et au type MIME les liens profonds de votre application.

Des liens profonds sont ajoutés à une destination en appelant la fonction deepLink qu'il contient le lambda de la destination. Il accepte la route en tant que type paramétré paramètre basePath pour le chemin de base de l'URL utilisée pour le lien profond.

Vous pouvez également ajouter une action et un type MIME à l'aide de la méthode deepLinkBuilder lambda de fin.

L'exemple suivant crée un URI de lien profond pour la destination 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/*"
  }
}

Format de l'URI

Le format de l'URI du lien profond est automatiquement généré à partir des champs de la route à l'aide des règles suivantes:

  • Les paramètres obligatoires sont ajoutés en tant que paramètres de chemin (exemple: /{id}).
  • Les paramètres associés à une valeur par défaut (paramètres facultatifs) sont ajoutés en tant que requêtes paramètres (exemple: ?name={name})
  • Les collections sont ajoutées en tant que paramètres de requête (exemple: ?items={value1}&items={value2})
  • L'ordre des paramètres correspond à l'ordre des champs de la route

Par exemple, le type d'itinéraire suivant:

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

possède le format d'URI généré suivant:

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

Vous pouvez ajouter autant de liens profonds que vous le souhaitez. Chaque fois que vous appelez deepLink(), un nouveau lien profond est ajouté à la liste correspondant à cette destination.

Limites

Le plug-in Safe Args n'est pas compatible avec le langage DSL Kotlin, car il recherche des fichiers de ressources XML pour générer les classes Directions et Arguments.