Navigare con i moduli delle funzionalità

La libreria Dynamic Navigator estende la funzionalità del componente Navigazione Jetpack per funzionare con le destinazioni definite nei moduli delle funzionalità. Questa libreria consente inoltre l'installazione immediata di moduli di funzionalità on demand durante la navigazione verso queste destinazioni.

Configurazione

Per supportare i moduli delle funzionalità, utilizza le seguenti dipendenze nel file build.gradle del modulo dell'app:

trendy

dependencies {
    def nav_version = "2.7.7"

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

Kotlin

dependencies {
    val nav_version = "2.7.7"

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

Tieni presente che le altre dipendenze di navigazione devono utilizzare le configurazioni dell'API in modo che siano disponibili per i moduli delle funzionalità.

Utilizzo di base

Per supportare i moduli delle funzionalità, modifica prima tutte le istanze di NavHostFragment nella tua app in androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment:

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
    app:navGraph="@navigation/nav_graph"
    ... />

Successivamente, aggiungi un attributo app:moduleName a tutte le destinazioni <activity>, <fragment> o <navigation> nei grafici di navigazione del modulo com.android.dynamic-feature associati a DynamicNavHostFragment. Questo attributo indica alla libreria di Dynamic Navigator che la destinazione appartiene a un modulo delle funzionalità con il nome da te specificato.

<fragment
    app:moduleName="myDynamicFeature"
    android:id="@+id/featureFragment"
    android:name="com.google.android.samples.feature.FeatureFragment"
    ... />

Quando navighi verso una di queste destinazioni, la libreria Dynamic Navigator verifica innanzitutto se il modulo delle funzionalità è installato. Se il modulo delle funzionalità è già presente, l'app raggiunge la destinazione come previsto. Se il modulo non è presente, la tua app mostra una destinazione del frammento di avanzamento intermedio durante l'installazione del modulo. L'implementazione predefinita del frammento di avanzamento mostra un'interfaccia utente di base con una barra di avanzamento e gestisce eventuali errori di installazione.

due schermate di caricamento che mostrano l&#39;interfaccia utente con una barra di avanzamento quando si accede per la prima volta a un modulo delle funzionalità
Figura 1. UI che mostra una barra di avanzamento quando un utente accede a una funzionalità on demand per la prima volta. L'app mostra questa schermata durante i download del modulo corrispondente.

Per personalizzare questa UI o per gestire manualmente l'avanzamento dell'installazione dalla schermata dell'app, consulta le sezioni Personalizzare il frammento di avanzamento e Monitorare lo stato della richiesta in questo argomento.

Le destinazioni che non specificano app:moduleName continuano a funzionare senza modifiche e si comportano come se la tua app utilizzasse un NavHostFragment normale.

Personalizza il frammento di avanzamento

Puoi ignorare l'implementazione del frammento di avanzamento per ogni grafico di navigazione impostando l'attributo app:progressDestination sull'ID della destinazione che vuoi utilizzare per gestire l'avanzamento dell'installazione. La destinazione personalizzata dell'avanzamento deve essere una Fragment che deriva da AbstractProgressFragment. Devi eseguire l'override dei metodi astratti per le notifiche relative a avanzamento dell'installazione, errori e altri eventi. Puoi quindi mostrare l'avanzamento dell'installazione in un'interfaccia utente a tua scelta.

La classe DefaultProgressFragment dell'implementazione predefinita utilizza questa API per mostrare l'avanzamento dell'installazione.

Monitorare lo stato della richiesta

La libreria di Dynamic Navigator consente di implementare un flusso UX simile a quello delle best practice UX per la distribuzione on demand, in cui un utente rimane nel contesto di una schermata precedente mentre è in attesa del completamento dell'installazione. Ciò significa che non devi mostrare affatto un'interfaccia utente intermedia o un frammento di avanzamento.

che mostra una barra di navigazione in basso con un&#39;icona che indica che è in corso il download di un modulo
         della funzionalità
Figura 2. Schermata che mostra l'avanzamento del download da una barra di navigazione in basso.

In questo scenario, hai la responsabilità di monitorare e gestire tutti gli stati dell'installazione, le modifiche all'avanzamento, gli errori e così via.

Per avviare questo flusso di navigazione non bloccante, passa un oggetto DynamicExtras che contiene un DynamicInstallMonitor a NavController.navigate(), come mostrato nell'esempio seguente:

Kotlin

val navController = ...
val installMonitor = DynamicInstallMonitor()

navController.navigate(
    destinationId,
    null,
    null,
    DynamicExtras(installMonitor)
)

Java

NavController navController = ...
DynamicInstallMonitor installMonitor = new DynamicInstallMonitor();

navController.navigate(
    destinationId,
    null,
    null,
    new DynamicExtras(installMonitor);
)

Subito dopo la chiamata a navigate(), devi controllare il valore di installMonitor.isInstallRequired per verificare se il tentativo di navigazione ha causato l'installazione di un modulo delle funzionalità.

  • Se il valore è false, stai navigando verso una destinazione normale e non devi fare altro.
  • Se il valore è true, dovresti iniziare a osservare l'oggetto LiveData che ora si trova in installMonitor.status. Questo oggetto LiveData emette aggiornamenti SplitInstallSessionState dalla libreria di base Play. Questi aggiornamenti contengono eventi di avanzamento dell'installazione che puoi utilizzare per aggiornare l'interfaccia utente. Ricorda di gestire tutti gli stati pertinenti come descritto nella guida di Play Core, inclusa la richiesta di conferma dell'utente, se necessario.

    Kotlin

    val navController = ...
    val installMonitor = DynamicInstallMonitor()
    
    navController.navigate(
      destinationId,
      null,
      null,
      DynamicExtras(installMonitor)
    )
    
    if (installMonitor.isInstallRequired) {
      installMonitor.status.observe(this, object : Observer<SplitInstallSessionState> {
          override fun onChanged(sessionState: SplitInstallSessionState) {
              when (sessionState.status()) {
                  SplitInstallSessionStatus.INSTALLED -> {
                      // Call navigate again here or after user taps again in the UI:
                      // navController.navigate(destinationId, destinationArgs, null, null)
                  }
                  SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
                      SplitInstallManager.startConfirmationDialogForResult(...)
                  }
    
                  // Handle all remaining states:
                  SplitInstallSessionStatus.FAILED -> {}
                  SplitInstallSessionStatus.CANCELED -> {}
              }
    
              if (sessionState.hasTerminalStatus()) {
                  installMonitor.status.removeObserver(this);
              }
          }
      });
    }
    

    Java

    NavController navController = ...
    DynamicInstallMonitor installMonitor = new DynamicInstallMonitor();
    
    navController.navigate(
      destinationId,
      null,
      null,
      new DynamicExtras(installMonitor);
    )
    
    if (installMonitor.isInstallRequired()) {
      installMonitor.getStatus().observe(this, new Observer<SplitInstallSessionState>() {
          @Override
          public void onChanged(SplitInstallSessionState sessionState) {
              switch (sessionState.status()) {
                  case SplitInstallSessionStatus.INSTALLED:
                      // Call navigate again here or after user taps again in the UI:
                      // navController.navigate(mDestinationId, mDestinationArgs, null, null);
                      break;
                  case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
                      SplitInstallManager.startConfirmationDialogForResult(...)
                      break;
    
                  // Handle all remaining states:
                  case SplitInstallSessionStatus.FAILED:
                      break;
                  case SplitInstallSessionStatus.CANCELED:
                      break;
              }
    
              if (sessionState.hasTerminalStatus()) {
                  installMonitor.getStatus().removeObserver(this);
              }
          }
      });
    }
    

Al termine dell'installazione, l'oggetto LiveData emette uno stato SplitInstallSessionStatus.INSTALLED. Dovresti quindi chiamare di nuovo NavController.navigate(). Poiché il modulo è stato installato, la chiamata ha esito positivo e l'app si sposta verso la destinazione come previsto.

Dopo aver raggiunto uno stato di terminale, ad esempio al termine dell'installazione o in caso di errore, devi rimuovere l'osservatore LiveData per evitare perdite di memoria. Puoi controllare se lo stato rappresenta uno stato del terminale utilizzando SplitInstallSessionStatus.hasTerminalStatus().

Vedi AbstractProgressFragment per un esempio di implementazione di questo osservatore.

Grafici inclusi

La libreria Dynamic Navigator supporta l'inclusione di grafici definiti nei moduli delle funzionalità. Per includere un grafico definito in un modulo delle funzionalità, procedi nel seguente modo:

  1. Utilizza <include-dynamic/> invece di <include/>, come mostrato nell'esempio seguente:

    <include-dynamic
        android:id="@+id/includedGraph"
        app:moduleName="includedgraphfeature"
        app:graphResName="included_feature_nav"
        app:graphPackage="com.google.android.samples.dynamic_navigator.included_graph_feature" />
    
  2. All'interno di <include-dynamic ... />, devi specificare i seguenti attributi:

    • app:graphResName: il nome del file delle risorse del grafico di navigazione. Il nome deriva dal nome del file del grafico. Ad esempio, se il grafico è in res/navigation/nav_graph.xml, il nome della risorsa è nav_graph.
    • android:id: l'ID di destinazione del grafico. La libreria Dynamic Navigator ignora eventuali valori android:id presenti nell'elemento principale del grafico incluso.
    • app:moduleName: il nome del pacchetto del modulo.

Utilizza il GraphPackage corretto

È importante ottenere il valore app:graphPackage corretto, in quanto, altrimenti, il componente di navigazione non sarà in grado di includere il valore navGraph specificato dal modulo delle funzionalità.

Il nome del pacchetto di un modulo di funzionalità dinamiche viene creato aggiungendo il nome del modulo a applicationId del modulo dell'app di base. Quindi, se il modulo dell'app di base ha un applicationId di com.example.dynamicfeatureapp e il modulo delle funzionalità dinamiche è denominato DynamicFeatureModule, il nome del pacchetto del modulo dinamico sarà com.example.dynamicfeatureapp.DynamicFeatureModule. Il nome del pacchetto è sensibile alle maiuscole.

In caso di dubbi, puoi confermare il nome del pacchetto del modulo della funzionalità controllando il valore AndroidManifest.xml generato. Dopo aver creato il progetto, vai su <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml, che dovrebbe avere un aspetto simile a questo:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dist="http://schemas.android.com/apk/distribution"
    featureSplit="DynamicFeatureModule"
    package="com.example.dynamicfeatureapp"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="30" />

    <dist:module
        dist:instant="false"
        dist:title="@string/title_dynamicfeaturemodule" >
        <dist:delivery>
            <dist:install-time />
        </dist:delivery>

        <dist:fusing dist:include="true" />
    </dist:module>

    <application />

</manifest>

Il valore featureSplit deve corrispondere al nome del modulo delle funzionalità dinamiche e il pacchetto corrisponderà al valore applicationId del modulo dell'app di base. app:graphPackage è la combinazione dei seguenti elementi: com.example.dynamicfeatureapp.DynamicFeatureModule.

È possibile accedere solo alla startDestination di un grafico di navigazione include-dynamic. Il modulo dinamico è responsabile del proprio grafico di navigazione e l'app di base non ne è a conoscenza.

Il meccanismo di inclusione-dinamica consente al modulo dell'app di base di includere un grafico di navigazione nidificato che viene definito all'interno del modulo dinamico. Questo grafico di navigazione nidificato si comporta come qualsiasi grafico di navigazione nidificato. Il grafico di navigazione principale (ossia il grafico principale del grafico nidificato) può definire solo il grafico di navigazione nidificato stesso come destinazione e non come i suoi elementi figlio. Pertanto, startDestination viene utilizzato quando il grafico di inclusione-navigazione dinamica è la destinazione.

Limitazioni

  • Al momento i grafici inclusi dinamicamente non supportano i link diretti.
  • I grafici nidificati caricati dinamicamente (ovvero un elemento <navigation> con app:moduleName) al momento non supportano i link diretti.