Mit Funktionsmodulen navigieren

Die Dynamic Navigator Library erweitert die Funktionalität der Jetpack-Navigationskomponente so, dass sie auch mit Zielen arbeitet, die in Funktionsmodulen definiert sind. Diese Bibliothek ermöglicht auch die nahtlose Installation von On-Demand-Funktionsmodulen beim Aufrufen dieser Ziele.

Einrichten

Verwende zur Unterstützung von Funktionsmodulen die folgenden Abhängigkeiten in der Datei build.gradle deines App-Moduls:

Groovig

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

Beachten Sie, dass die anderen Navigationsabhängigkeiten API-Konfigurationen verwenden sollten, damit sie für Ihre Funktionsmodule verfügbar sind.

Grundlegende Verwendung

Zur Unterstützung von Featuremodulen müssen Sie zuerst alle Instanzen von NavHostFragment in Ihrer App zu androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment ändern:

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

Als Nächstes fügen Sie ein app:moduleName-Attribut allen <activity>-, <fragment>- oder <navigation>-Zielen in den Navigationsdiagrammen des com.android.dynamic-feature-Moduls hinzu, die mit einem DynamicNavHostFragment verknüpft sind. Dieses Attribut informiert die Dynamic Navigator-Bibliothek darüber, dass das Ziel zu einem Funktionsmodul mit dem von Ihnen angegebenen Namen gehört.

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

Wenn Sie eines dieser Ziele aufrufen, prüft die Dynamic Navigator-Bibliothek zuerst, ob das Funktionsmodul installiert ist. Wenn das Funktionsmodul bereits vorhanden ist, navigiert Ihre App wie erwartet zum Ziel. Wenn das Modul nicht vorhanden ist, zeigt Ihre Anwendung bei der Installation des Moduls ein Zwischenziel für das Fortschrittsfragment an. Die Standardimplementierung des Fortschrittsfragments zeigt eine einfache Benutzeroberfläche mit einer Fortschrittsanzeige und behandelt etwaige Installationsfehler.

Zwei Ladebildschirme, auf denen eine Benutzeroberfläche mit einer Fortschrittsanzeige zu sehen ist, wenn zum ersten Mal ein Funktionsmodul aufgerufen wird
Abbildung 1: Benutzeroberfläche, auf der eine Fortschrittsanzeige zu sehen ist, wenn ein Nutzer zum ersten Mal eine On-Demand-Funktion aufruft. Die App zeigt diesen Bildschirm an, während das entsprechende Modul heruntergeladen wird.

In den Abschnitten Fortschrittsfragment anpassen und Anfragestatus überwachen erfahren Sie, wie Sie diese UI anpassen oder den Installationsfortschritt manuell über Ihren eigenen App-Bildschirm verarbeiten.

Ziele ohne app:moduleName funktionieren weiterhin ohne Änderungen und verhalten sich so, als würde in der Anwendung ein reguläres NavHostFragment verwendet werden.

Fortschrittsfragment anpassen

Sie können die Implementierung des Fortschrittsfragments für jede Navigationsgrafik überschreiben. Dazu setzen Sie das Attribut app:progressDestination auf die ID des Ziels, das für die Verarbeitung des Installationsfortschritts verwendet werden soll. Das benutzerdefinierte Fortschrittsziel sollte ein Fragment sein, der von AbstractProgressFragment abgeleitet wird. Für Benachrichtigungen über den Installationsfortschritt, Fehler und andere Ereignisse müssen Sie die abstrakten Methoden überschreiben. Sie können den Installationsfortschritt dann in einer UI Ihrer Wahl anzeigen.

Die Klasse DefaultProgressFragment der Standardimplementierung verwendet diese API, um den Installationsfortschritt anzuzeigen.

Anfragestatus überwachen

Mit der Dynamic Navigator-Bibliothek können Sie einen UX-Ablauf implementieren, der dem unter UX-Best Practices für On-Demand-Bereitstellung ähnelt, bei dem der Nutzer im Kontext eines vorherigen Bildschirms bleibt, während er auf den Abschluss der Installation wartet. Das bedeutet, dass keine Zwischen-UI und kein Fortschrittsfragment angezeigt werden müssen.

Bildschirm mit einer unteren Navigationsleiste mit einem Symbol, das anzeigt, dass ein Funktionsmodul heruntergeladen wird
Abbildung 2: Bildschirm, der in einer unteren Navigationsleiste den Fortschritt des Downloads anzeigt.

In diesem Szenario sind Sie dafür verantwortlich, alle Installationsstatus, Fortschrittsänderungen, Fehler usw. zu überwachen und zu verarbeiten.

Um diesen nicht blockierenden Navigationsfluss zu starten, übergeben Sie ein DynamicExtras-Objekt, das einen DynamicInstallMonitor enthält, an NavController.navigate(), wie im folgenden Beispiel gezeigt:

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

Direkt nach dem Aufrufen von navigate() sollten Sie den Wert von installMonitor.isInstallRequired prüfen, um festzustellen, ob der versuchte Navigationsversuch zur Installation eines Feature-Moduls geführt hat.

  • Wenn der Wert false ist, werden Sie zu einem normalen Ziel navigiert und müssen nichts weiter tun.
  • Wenn der Wert true ist, sollten Sie das LiveData-Objekt beobachten, das sich jetzt in installMonitor.status befindet. Dieses LiveData-Objekt sendet SplitInstallSessionState-Updates aus der Play Core-Bibliothek. Diese Updates enthalten Installationsfortschrittsereignisse, mit denen Sie die Benutzeroberfläche aktualisieren können. Denken Sie daran, alle relevanten Status zu verarbeiten, wie im Play Core-Leitfaden beschrieben. Gegebenenfalls müssen Sie auch die Nutzerbestätigung anfordern.

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

Nach Abschluss der Installation gibt das LiveData-Objekt den Status SplitInstallSessionStatus.INSTALLED aus. Rufen Sie dann noch einmal NavController.navigate() auf. Da das Modul jetzt installiert ist, ist der Aufruf jetzt erfolgreich und die Anwendung ruft wie erwartet das Ziel auf.

Wenn Sie einen Terminalstatus erreicht haben, z. B. wenn die Installation abgeschlossen ist oder die Installation fehlschlägt, sollten Sie den LiveData-Beobachter entfernen, um Speicherlecks zu vermeiden. Mit SplitInstallSessionStatus.hasTerminalStatus() können Sie prüfen, ob der Status einen Terminalstatus darstellt.

Eine Beispielimplementierung dieses Beobachters finden Sie unter AbstractProgressFragment.

Eingeschlossene Grafiken

Die Dynamic Navigator-Bibliothek unterstützt das Einbinden von Grafiken, die in Featuremodulen definiert sind. So fügen Sie ein Diagramm ein, das in einem Funktionsmodul definiert ist:

  1. Verwenden Sie <include-dynamic/> anstelle von <include/>, wie im folgenden Beispiel gezeigt:

    <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. Innerhalb von <include-dynamic ... /> müssen die folgenden Attribute angegeben werden:

    • app:graphResName: der Name der Ressourcendatei für das Navigationsdiagramm. Der Name wird aus dem Dateinamen der Grafik abgeleitet. Wenn sich das Diagramm beispielsweise in res/navigation/nav_graph.xml befindet, lautet der Ressourcenname nav_graph.
    • android:id: die Ziel-ID der Grafik Die Dynamic Navigator-Bibliothek ignoriert alle android:id-Werte im Stammelement der enthaltenen Grafik.
    • app:moduleName: der Paketname des Moduls.

Verwende das richtige Diagramm-Paket

Die app:graphPackage muss korrekt sein, da die Navigationskomponente sonst nicht die angegebene navGraph aus dem Funktionsmodul einbinden kann.

Der Paketname eines dynamischen Funktionsmoduls wird erstellt, indem der Name des Moduls an die applicationId des Basis-App-Moduls angehängt wird. Wenn also das applicationId des Basisanwendungsmoduls com.example.dynamicfeatureapp und das dynamische Funktionsmodul den Namen DynamicFeatureModule hat, ist der Paketname des dynamischen Moduls com.example.dynamicfeatureapp.DynamicFeatureModule. Bei diesem Paketnamen wird die Groß-/Kleinschreibung beachtet.

Im Zweifelsfall kannst du den Paketnamen des Funktionsmoduls bestätigen. Prüfe dazu das generierte AndroidManifest.xml. Rufen Sie nach dem Erstellen des Projekts <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml auf. Dies sollte in etwa so aussehen:

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

Der Wert für featureSplit sollte mit dem Namen des dynamischen Funktionsmoduls übereinstimmen und das Paket entspricht dem applicationId des Basis-App-Moduls. app:graphPackage ist die Kombination aus diesen Elementen: com.example.dynamicfeatureapp.DynamicFeatureModule.

Es ist nur möglich, zum startDestination einer include-dynamic-Navigationsgrafik zu wechseln. Das dynamische Modul ist für sein eigenes Navigationsdiagramm verantwortlich und die Basis-App hat keine Kenntnis davon.

Der Mechanismus zum Einbeziehen dynamischer Elemente ermöglicht, dass das Basis-Anwendungsmodul eine verschachtelte Navigationsgrafik einbinden kann, die im dynamischen Modul definiert ist. Diese verschachtelte Navigationsgrafik funktioniert wie jede andere verschachtelte Navigationsgrafik. Die Stammnavigationsgrafik, also das übergeordnete Element der verschachtelten Grafik, kann nur die verschachtelte Navigationsgrafik selbst als Ziel und nicht als ihre untergeordneten Elemente definieren. Daher wird startDestination verwendet, wenn die dynamische Navigationsgrafik das Ziel ist.

Einschränkungen

  • In Diagrammen mit dynamisch eingebundenen Inhalten werden derzeit keine Deeplinks unterstützt.
  • Dynamisch geladene verschachtelte Grafiken, also ein <navigation>-Element mit einem app:moduleName-Element, unterstützen derzeit keine Deeplinks.