Crea un grafico in modo programmatico utilizzando la Kotlin DSL

Il componente Navigazione fornisce una lingua specifica del dominio basata su Kotlin, oppure DSL, che si basa sulla tecnologia generatori sicuri per il tipo. Questa API ti consente di comporre in modo dichiarativo il grafico nel codice Kotlin, anziché all'interno di una risorsa XML. Questo può essere utile se vuoi creare la navigazione dell'app in modo dinamico. Ad esempio, la tua app potrebbe scaricare e memorizzare nella cache una configurazione di navigazione da un servizio web esterno e usare questa configurazione per creare dinamicamente un grafico di navigazione nel Funzione onCreate().

Dipendenze

Per utilizzare la DSL Kotlin, aggiungi la seguente dipendenza al parametro File build.gradle:

Alla moda

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

Creazione di un grafico

Iniziamo con un esempio di base basato App Sunflower. Per questo ad esempio, abbiamo due destinazioni: home e plant_detail. home è presente quando l'utente avvia l'app per la prima volta. Questa destinazione mostra un elenco di piante del giardino dell'utente. Quando l'utente seleziona uno di piante, l'app raggiunge la destinazione plant_detail.

La figura 1 mostra queste destinazioni insieme agli argomenti richiesti dalla plant_detail destinazione e un'azione, to_plant_detail, utilizzata dall'app per navigare da home a plant_detail.

L'app Girasole ha due destinazioni con un'azione
            li collegano.
Figura 1. L'app Girasole ha due destinazioni: home e plant_detail, insieme a un'azione che li connette.

Hosting di un grafico di navigazione DSL Kotlin

Prima di poter creare il grafico di navigazione della tua app, devi disporre di una posizione nel grafico. Questo esempio utilizza frammenti, quindi ospita il grafo in una NavHostFragment all'interno di un 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>

Tieni presente che in questo esempio l'attributo app:navGraph non è impostato. Il grafico non è definito come resource nel res/navigation quindi deve essere impostata come parte di onCreate() processo nell'attività.

In XML, un'azione collega un ID destinazione a uno o più argomenti. Tuttavia, quando utilizzi la navigazione DSL, una route può contenere argomenti come parte lungo il percorso. Ciò significa che non esiste un concetto di azioni quando si utilizza la DSL.

Il passaggio successivo consiste nel definire alcune costanti da utilizzare per definire nel grafico.

Creare costanti per il grafico

Grafici di navigazione basati su XML vengono analizzati come parte del processo di compilazione di Android. Viene creata una costante numerica per ogni attributo id definito nel grafico. Queste immagini statiche generate in tempo di build Gli ID non sono disponibili durante la creazione del grafico di navigazione in fase di runtime, quindi Navigazione DSL utilizza stringhe di route anziché ID. Ogni percorso è rappresentato da una stringa univoca ed è buona norma definirle come costanti per ridurre il rischio di errori di battitura.

Quando si tratta di argomenti, questi sono incorporato nella stringa della route. L'integrazione di questa logica nella route può, ancora una volta, ridurre il rischio di si insinuano bug legati agli errori di battitura.

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

Una volta definite le costanti, puoi creare la navigazione grafico.

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 questo esempio, il lambda finale definisce due destinazioni di frammenti utilizzando il comando fragment() Funzione di builder DSL. Questa funzione richiede una stringa di route per la destinazione che si ottiene dalle costanti. La funzione accetta anche un'istanza lambda per una configurazione aggiuntiva, come l'etichetta di destinazione, nonché le funzioni del builder incorporate per argomenti e link diretti.

La classe Fragment che gestisce l'interfaccia utente di ogni destinazione che viene passata come tipo con parametri all'interno parentesi angolari (<>). Ha lo stesso effetto dell'impostazione di android:name sulle destinazioni dei frammenti definite utilizzando XML.

Infine, puoi navigare da home a plant_detail utilizzando NavController.navigation() chiamate:

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

In PlantDetailFragment, puoi ottenere il valore dell'argomento come mostrato nel seguente esempio:

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

I dettagli su come fornire gli argomenti durante la navigazione sono disponibili nella fornisci argomenti di destinazione.

Il resto della guida descrive gli elementi comuni dei grafici di navigazione, le destinazioni e come utilizzarle durante la creazione del grafico.

Destinazioni

La DSL Kotlin fornisce supporto integrato per tre tipi di destinazione: Destinazioni Fragment, Activity e NavGraph, ciascuna delle quali ha le proprie funzione di estensione in linea disponibile per creare e configurare destinazione.

Destinazioni dei frammenti

La fragment() La funzione DSL può essere parametrizzata nella classe del frammento di implementazione e prende una stringa di route univoca da assegnare a questa destinazione, seguita da una lambda in cui puoi fornire un'ulteriore configurazione, come descritto Navigazione con il grafico Kotlin DSL .

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

Destinazione dell'attività

La activity() La funzione DSL prende una stringa di route univoca per assegnare la destinazione a questa destinazione, ma non parametri in nessuna classe di attività di implementazione. Puoi invece impostare activityClass facoltativo in un lambda finale. Questa flessibilità ti consente di definire una destinazione per un'attività che deve essere avviata utilizzando un intent implicito, per cui una classe di attività esplicita non ha senso. Come per le destinazioni dei frammenti, puoi anche configurare un'etichetta, argomenti e link diretti.

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

   activityClass = ActivityDestination::class
}

La navigation() La funzione DSL può essere utilizzata per creare una grafico di navigazione nidificato: Questa funzione accetta tre argomenti: una route a assegnare al grafico il percorso della destinazione di partenza del grafico e una lambda per configurare ulteriormente il grafico. Gli elementi validi includono altre destinazioni, argomenti, link diretti e un etichetta descrittiva della destinazione. Questa etichetta può essere utile per associare il grafico di navigazione all'interfaccia utente. componenti utilizzando UI di navigazione

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

Supporto di destinazioni personalizzate

Se utilizzi un nuovo tipo di destinazione che non il supporto diretto per Kotlin DSL, puoi aggiungere queste destinazioni al tuo DSL con addDestination():

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

In alternativa, puoi anche utilizzare l'operatore unario più per aggiungere una nuova destinazione creata direttamente nel grafico:

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

Fornitura di argomenti di destinazione

Qualsiasi destinazione può definire argomenti facoltativi o obbligatori. Azioni può essere definito utilizzando argument() funzione su NavDestinationBuilder, che è la classe base per tutte del generatore di destinazioni. Questa funzione prende il nome dell'argomento come stringa e una funzione lambda utilizzata per costruire e configurare NavArgument

All'interno di lambda puoi specificare il tipo di dati dell'argomento, un valore predefinito se applicabile e se è o meno 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
    }
}

Se viene specificato un valore defaultValue, è possibile dedurre il tipo. Se viene usato sia un defaultValue e type, i tipi devono corrispondere. Consulta le documentazione di riferimento di NavType per un un elenco completo dei tipi di argomenti disponibili.

Fornitura di tipi personalizzati

Alcuni tipi, ad esempio ParcelableType e SerializableType, non supportano l'analisi dei valori delle stringhe utilizzate da route o link diretti. perché non fanno affidamento sulla riflessione in fase di runtime. Fornendo un modello di NavType, puoi controllare esattamente il modo in cui il tipo viene analizzato da una route link diretto. Ciò consente di utilizzare Serializzazione di Kotlin o altro per fornire la codifica e la decodifica senza riflessioni del tuo tipo personalizzato.

Ad esempio, una classe di dati che rappresenta i parametri di ricerca trasmessi al tuo schermata di ricerca potrebbe implementare sia Serializable (per fornire il codifica/decodifica) e Parcelize (per supportare il salvataggio e il ripristino da un Bundle):

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

Un NavType personalizzato potrebbe essere scritto come:

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

Questa opzione può quindi essere utilizzata nella tua DSL Kotlin come qualsiasi altro tipo:

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 include sia la scrittura che la lettura di ogni campo, significa che devi usare NavType anche quando accedi alla per assicurarti che i formati corrispondano:

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

Il parametro può essere ottenuto dagli argomenti nella destinazione:

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

Link diretti

I link diretti possono essere aggiunti a qualsiasi destinazione, proprio come con un file XML grafico di navigazione. Tutte le stesse procedure definite Creazione di un link diretto per una destinazione si applicano al processo di creazione link diretto esplicito utilizzando Kotlin DSL.

Quando crei un link diretto implicito Tuttavia, non hai una risorsa di navigazione XML che possa essere analizzata <deepLink> elementi. Pertanto, non puoi fare affidamento sul posizionamento di un <nav-graph> nel file AndroidManifest.xml e deve invece aggiungere filtri di intent manuali alla tua attività. Il filtro per intent fornito deve corrispondere al pattern URL di base, all'azione e tipo MIME dei link diretti dell'app.

Puoi fornire un valore deeplink più specifico per ogni singolo link diretto di destinazione utilizzando deepLink() Funzione DSL. Questa funzione accetta un NavDeepLink che contiene un String che rappresenta il pattern URI, String che rappresenta le azioni dell'intent e String che rappresenta il mimeType .

Ad esempio:

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

Non esiste un limite al numero di link diretti che puoi aggiungere. Ogni volta che chiami deepLink() viene aggiunto un nuovo link diretto a un elenco gestito per quella destinazione.

Uno scenario di link diretti impliciti più complesso che definisce anche i parametri basati su query sono riportati di seguito:

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

Puoi utilizzare interpolazione di stringhe per semplificarne la definizione.

Limitazioni

Il plug-in Safe Args incompatibile con il Kotlin DSL, in quanto il plug-in cerca i file di risorse XML generano le classi Directions e Arguments.

Scopri di più

Consulta la sezione Sicurezza del tipo di navigazione per scoprire come garantire la sicurezza dei tipi per il tuo DSL Kotlin e Codice di Navigazione Compose.