Framments e Kotlin DSL

Il componente Navigazione fornisce una lingua specifica del dominio basata su Kotlin, oppure DSL, che si basa sulla tecnologia type-safe di Kotlin costruttori di Google. Questa API ti consente di comporre in modo dichiarativo il grafico nel codice Kotlin, anziché che all'interno di una risorsa XML. Questo può essere utile se vuoi creare la navigazione della tua 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 poi utilizzare questa configurazione per creare dinamicamente un grafo di navigazione nella funzione onCreate() della tua attività.

Dipendenze

Per utilizzare Kotlin DSL con Fragments, aggiungi la seguente dipendenza al file File build.gradle:

Groovy

dependencies {
    def nav_version = "2.9.2"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.9.2"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

Creazione di un grafico

Ecco un esempio di base basato sulla Girasole Google Cloud. Per questo ad 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 passa alla 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 Sunflower ha due destinazioni, home e plant_detail, insieme a un'azione che le collega.

Hosting di un grafico di navigazione DSL Kotlin

Prima di poter creare il grafico di navigazione della tua app, devi disporre di un luogo in cui grafico. Questo esempio utilizza i frammenti, quindi ospita il grafico in un 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 l'attributo app:navGraph non è impostato in questo esempio. Il grafico non è definito come risorsa nella cartella res/navigation, pertanto deve essere impostato nell'ambito del processo onCreate() nell'attività.

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

Il passaggio successivo consiste nel definire i percorsi che utilizzerai per definire il gráfo.

Creare percorsi 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 id attributo definito nel grafico. Questi ID statici generati in fase di compilazione non sono disponibili quando crei il grafo di navigazione in fase di esecuzione, pertanto il Navigation DSL utilizza tipi serializzabili anziché ID. Ogni percorso è rappresentato da un tipo univoco.

Quando si gestiscono argomenti, questi vengono incorporati nel percorso del testo. In questo modo puoi impostare la sicurezza del tipo per gli argomenti di navigazione.

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

Una volta definiti i percorsi, puoi creare il grafico di navigazione.

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 questo esempio, vengono definite due destinazioni di frammenti utilizzando il metodo fragment() Funzione di builder DSL. Questa funzione richiede due argomenti di Google.

Innanzitutto, un corso Fragment. che fornisce la UI per questa destinazione. Questa impostazione ha lo stesso effetto dell'impostazione dell'attributo android:name sulle destinazioni dei frammenti definite utilizzando il codice XML.

Il secondo è il percorso. Deve essere un tipo serializzabile che si estende da Any. Deve contenere tutti gli argomenti di navigazione che verranno utilizzati da questa destinazione e i relativi tipi.

La funzione accetta anche un parametro lambda facoltativo per una configurazione aggiuntiva, come l'etichetta di destinazione, nonché funzioni di creazione incorporate per argomenti personalizzati e link diretti.

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

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

In PlantDetailFragment, puoi ottenere gli argomenti di navigazione ottenendo l'attuale NavBackStackEntry e chiamando toRoute per ottenere l'istanza del percorso.

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

Se PlantDetailFragment utilizza un ViewModel, ottieni l'istanza del route utilizzando SavedStateHandle.toRoute.

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

Nella parte rimanente di questa guida vengono descritti gli elementi comuni dei grafici di navigazione, le destinazioni e come utilizzarli 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 con la classe del frammento per l'interfaccia utente e la tipo di route utilizzato per identificare in modo univoco questa destinazione, seguito da una lambda in cui puoi fornire un'ulteriore configurazione come descritto nella Navigazione con il grafico Kotlin DSL.

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

Destinazione dell'attività

La funzione DSL activity() accetta un parametro di tipo per il percorso, ma non è parametrizzata per qualsiasi classe di attività di implementazione. Puoi invece impostare un valore facoltativo per il campo activityClass un lambda finale. Questa flessibilità ti consente di definire una destinazione per un'attività che deve essere avviata utilizzando un intent implicito, in cui un'attività esplicita non avrebbe senso. Come per le destinazioni dei frammenti, puoi anche configurare un'etichetta, argomenti personalizzati e link diretti.

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

   activityClass = MyActivity::class
}

La funzione navigation() DSL può essere utilizzata per creare un grafo di navigazione nidificato. Questa funzione accetta un parametro di tipo per il percorso da assegnare a questo grafo. Richiede anche due argomenti: il percorso della destinazione di partenza del grafico e una lambda per e configurare il grafico. Gli elementi validi includono altre destinazioni, tipi di argomenti personalizzati, link diretti e un'etichetta descrittiva per la destinazione. Questa etichetta può essere utile per associare il grafo di navigazione ai componenti dell'interfaccia utente utilizzando NavigationUI.

@Serializable data object HomeGraph
@Serializable data object Home

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

Supporto di destinazioni personalizzate

Se utilizzi un nuovo tipo di destinazione che non supporta direttamente il DSL Kotlin, puoi aggiungere queste destinazioni al tuo 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 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
}

Fornire gli argomenti di destinazione

Gli argomenti di destinazione possono essere definiti all'interno della classe Route. Questi possono essere esattamente come lo faresti per qualsiasi classe Kotlin. Gli argomenti obbligatori sono i tipi non nulli e gli argomenti facoltativi sono definiti con e i relativi valori.

Il meccanismo sottostante per la rappresentazione delle route e dei relativi argomenti si basa su stringhe. L'utilizzo di stringhe per modellare le route consente di memorizzare e ripristinare lo stato di navigazione dal disco durante le modifiche della configurazione e l'interruzione del processo avviata dal sistema. Per questo motivo, ogni argomento di navigazione deve essere serializzabile, cioè deve avere che converte la rappresentazione in memoria del valore dell'argomento in un String.

Il plug-in di serializzazione Kotlin genera automaticamente metodi di serializzazione per i tipi di base quando l'annotazione @Serializable viene aggiunta a un oggetto.

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

fragment<MyFragment, MyRoute>

Fornire tipi personalizzati

Per i tipi di argomenti personalizzati, devi fornire una classe NavType personalizzata. Questo ti consente di controllare esattamente il modo in cui il tipo viene analizzato da una route o un link diretto.

Ad esempio, un percorso utilizzato per definire una schermata di ricerca potrebbe contenere una classe che rappresenta i parametri di ricerca:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

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

Quando raggiungi la destinazione, crea un'istanza del percorso:

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

Il parametro può essere ottenuto dalla route nella destinazione:

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

Link diretti

I link diretti possono essere aggiunti a qualsiasi destinazione, proprio come con un file XML grafico di navigazione. Tutte le procedure descritte nella sezione Creazione di un link diretto per una destinazione si applicano al processo di creazione di un link diretto utilizzando il 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 tuo file AndroidManifest.xml e al suo posto deve aggiungere l'intent filtri manualmente alle tue attività. Lo scopo che fornisci deve corrispondere al percorso di base, all'azione e al tipo MIME link diretti dell'app.

I link diretti vengono aggiunti a una destinazione richiamando la funzione deepLink all'interno il lambda della destinazione. Accetta la route come tipo con parametri e un parametro basePath per il percorso di base dell'URL utilizzato per il link diretto.

Puoi anche aggiungere un'azione e un tipo MIME utilizzando deepLinkBuilder lambda finale.

L'esempio seguente crea un URI di link diretto per la destinazione 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/*"
  }
}

Formato URI

Il formato dell'URI del link diretto viene generato automaticamente dai campi del percorso utilizzando le seguenti regole:

  • I parametri obbligatori vengono aggiunti come parametri di percorso (ad es. /{id})
  • I parametri con un valore predefinito (parametri facoltativi) vengono aggiunti come parametri di query (esempio: ?name={name}).
  • Le raccolte vengono aggiunte come parametri di query (ad esempio: ?items={value1}&items={value2})
  • L'ordine dei parametri corrisponde all'ordine dei campi nel route

Ad esempio, il seguente tipo di route:

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

ha un formato URI generato:

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

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.

Limitazioni

Il plug-in Safe Args è incompatibile con il DSL Kotlin, in quanto cerca file di risorse XML per generare classi Directions e Arguments.