Il componente Navigazione fornisce un linguaggio specifico del dominio basato su Kotlin, o DSL, che si basa sui generatori sicuri per il tipo di Kotlin.
Questa API ti consente di comporre in modo dichiarativo il grafico nel codice Kotlin,
anziché all'interno di una risorsa XML. Può essere utile se vuoi creare
la navigazione dell'app in modo dinamico. Ad esempio, l'app potrebbe scaricare e
memorizzare nella cache una configurazione di navigazione da un servizio web esterno e poi utilizzare
questa configurazione per creare dinamicamente un grafico di navigazione nella funzione
onCreate()
dell'attività.
Dipendenze
Per utilizzare il DSL Kotlin, aggiungi la seguente dipendenza al file build.gradle
dell'app:
trendy
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
sull'app Sunflower. Per questo
esempio, abbiamo due destinazioni: home
e plant_detail
. La destinazione home
è presente quando l'utente avvia l'app per la prima volta. Questa destinazione
visualizza un elenco di piante del giardino dell'utente. Quando l'utente seleziona una delle
piante, l'app raggiunge la destinazione plant_detail
.
La figura 1 mostra queste destinazioni insieme agli argomenti richiesti dalla
destinazione plant_detail
e da un'azione, to_plant_detail
, utilizzata dall'app
per passare da home
a plant_detail
.
![L'app Girasole ha due destinazioni con un'azione che le collega.](https://developer.android.google.cn/static/images/guide/navigation/navigation-kotlin-dsl-1.png?authuser=6&hl=it)
home
e plant_detail
, e un'azione che le collega.Hosting di un grafico di navigazione DSL Kotlin
Prima di poter creare il grafico di navigazione dell'app, è necessario un luogo in cui ospitarlo. Questo esempio utilizza frammenti, quindi ospita il grafico 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 risorsa nella cartella res/navigation
, quindi deve essere impostato come parte del processo onCreate()
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 della route. Ciò significa che non esiste un concetto di azioni quando si utilizza la DSL.
Il passaggio successivo consiste nel definire alcune costanti da utilizzare durante la definizione del grafico.
Creare costanti per il grafico
I 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. Questi ID statici generati in fase di build non sono disponibili durante la creazione del grafico di navigazione in fase di runtime, quindi il DSL di navigazione utilizza stringhe di route anziché ID. Ogni route è rappresentata da una stringa univoca ed è buona norma definirle come costanti per ridurre il rischio di bug correlati a errori di battitura.
Quando si gestiscono argomenti, questi vengono incorporati nella stringa di route. L'integrazione di questa logica nella route può, ancora una volta, ridurre il rischio di intrusione di bug correlati a 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"
}
Creazione di un grafico con il DSL NavGraphBuilder
Dopo aver definito le costanti, puoi creare il grafico di navigazione.
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 la funzione di creazione DSL fragment()
. Questa funzione richiede una stringa di route
per la destinazione ottenuta dalle costanti. La funzione accetta anche un lambda facoltativo per la configurazione aggiuntiva, come l'etichetta di destinazione, nonché funzioni di creazione incorporate per argomenti e link diretti.
La classe Fragment
che gestisce l'interfaccia utente di ogni destinazione viene passata come tipo con parametri all'interno di parentesi angolari (<>
). Questo ha lo stesso effetto dell'impostazione dell'attributo android:name
sulle destinazioni di frammenti definite utilizzando XML.
Navigazione con il grafico DSL Kotlin
Infine, puoi navigare da home
a plant_detail
utilizzando le chiamate
NavController.navigation()
standard:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
In PlantDetailFragment
, puoi ottenere il valore dell'argomento, come mostrato nell'esempio seguente:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
I dettagli su come fornire gli argomenti durante la navigazione sono disponibili nella sezione Fornisci gli argomenti di destinazione.
Il resto della guida descrive gli elementi comuni dei grafici di navigazione, le destinazioni e come utilizzarli durante la creazione del grafico.
Destinazioni
La DSL Kotlin offre supporto integrato per tre tipi di destinazione:
Fragment
, Activity
e NavGraph
destinazioni, ognuna delle quali ha una propria
funzione di estensione in linea disponibile per creare e configurare
la destinazione.
Destinazioni dei frammenti
La funzione DSL fragment()
può essere parametrizzata nella classe del frammento di implementazione e utilizza una stringa di route univoca da assegnare a questa destinazione, seguita da una stringa lambda in cui puoi fornire una configurazione aggiuntiva, come descritto nella sezione Navigazione con il grafico DSL Kotlin.
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
Destinazione dell'attività
La funzione DSL activity()
richiede una stringa di route univoca da assegnare a questa destinazione, ma non è parametrizzata per alcuna classe di attività di implementazione. Puoi invece impostare un
activityClass
facoltativo in un lambda finale. Questa flessibilità consente di definire una destinazione per un'attività che deve essere avviata utilizzando un intent implicito, per cui una classe di attività esplicita non avrebbe senso. Come per le destinazioni dei frammenti,
puoi anche configurare etichetta, argomenti e link diretti.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
Destinazione grafico di navigazione
La funzione DSL navigation()
può essere utilizzata per creare un grafico di navigazione nidificato.
Questa funzione accetta tre argomenti: una route da assegnare al grafico, la route della destinazione iniziale del grafico e una route 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 ai componenti dell'interfaccia utente utilizzando NavigatorUI.
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 supporta direttamente la DSL Kotlin, puoi aggiungere queste destinazioni alla rete DSL Kotlin utilizzando addDestination()
:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
In alternativa, puoi utilizzare l'operatore unario più per aggiungere una destinazione di nuova costruzione direttamente al 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. Le azioni
possono essere definite utilizzando la
funzione argument()
su NavDestinationBuilder
, che è la classe base per tutti
i tipi del generatore di destinazioni. Questa funzione prende il nome dell'argomento come stringa e una funzione lambda utilizzata per costruire e configurare una NavArgument
.
All'interno di lambda puoi specificare il tipo di dati dell'argomento, un valore predefinito, se applicabile, e se è possibile specificare un valore 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 vengono specificati sia defaultValue
sia type
, i tipi devono corrispondere. Consulta la documentazione di riferimento di NavType per un elenco completo dei tipi di argomenti disponibili.
Fornitura di tipi personalizzati
Alcuni tipi, come 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. Se fornisci una classe NavType
personalizzata, puoi controllare esattamente il modo in cui il tipo viene analizzato da una route o da un link diretto. In questo modo puoi utilizzare la
serie di serie di Kotlin o altre
librerie per fornire una codifica e decodifica senza riflessioni del tuo tipo personalizzato.
Ad esempio, una classe dati che rappresenta i parametri di ricerca trasmessi alla schermata di ricerca potrebbe implementare sia Serializable
(per fornire il supporto di codifica/decodifica) sia 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, il che significa che NavType
deve essere utilizzato anche quando accedi alla destinazione per garantire la corrispondenza dei formati:
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 per un grafico di navigazione basata su XML. Tutte le stesse procedure definite in Creazione di un link diretto per una destinazione si applicano al processo di creazione di un link diretto esplicito utilizzando il DSL di Kotlin.
Tuttavia, quando crei un link diretto implicito,
non hai una risorsa di navigazione XML che può essere analizzata per
gli elementi <deepLink>
. Pertanto, non puoi fare affidamento sul posizionamento di un elemento <nav-graph>
nel file AndroidManifest.xml
, ma devi aggiungere manualmente filtri di intent alle tue attività.
Il filtro per intent che fornisci deve corrispondere al pattern URL di base, all'azione e al
tipo MIME dei link diretti dell'app.
Puoi fornire un valore deeplink
più specifico per ogni destinazione con collegamento diretto individuale utilizzando la funzione DSL deepLink()
. Questa funzione accetta un NavDeepLink
che contiene un String
che rappresenta il pattern URI, un String
che rappresenta le azioni di intent e un
String
che rappresenta il mimeType .
Ecco alcuni esempi:
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()
, un nuovo link diretto viene aggiunto a un elenco gestito per quella destinazione.
Di seguito è riportato uno scenario di link diretto implicito più complesso che definisce anche i parametri basati su query e percorso:
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 l'interpolazione di stringhe per semplificare la definizione.
Limitazioni
Il plug-in Safe Args non è compatibile con il DSL Kotlin, in quanto cerca i file di risorse XML per generare le classi Directions
e Arguments
.
Scopri di più
Consulta la pagina Sicurezza del tipo di navigazione per scoprire come garantire la sicurezza dei tipi per il tuo codice DSL Kotlin e Navigator Compose.