Nawigacja z użyciem modułów funkcji

Biblioteka Dynamic Navigator rozszerza możliwości komponentu Jetpack Nawigacja, aby umożliwić działanie z miejscami docelowymi zdefiniowanymi w modułach funkcji. Ta biblioteka umożliwia też bezproblemową instalację modułów funkcji na żądanie podczas przechodzenia do tych miejsc.

Skonfiguruj

Aby obsługiwać moduły funkcji, użyj tych zależności w pliku build.gradle modułu aplikacji:

Odlotowy

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

Pamiętaj, że pozostałe zależności Nawigacji powinny korzystać z konfiguracji interfejsu API, aby były dostępne dla modułów funkcji.

Podstawowe użycie

Aby obsługiwać moduły funkcji, najpierw zmień wszystkie wystąpienia NavHostFragment w aplikacji na 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"
    ... />

Następnie dodaj atrybut app:moduleName do dowolnych miejsc docelowych <activity>, <fragment> lub <navigation> na wykresach nawigacyjnych modułu com.android.dynamic-feature, które są powiązane z elementem DynamicNavHostFragment. Ten atrybut informuje bibliotekę Dynamic Navigator, że miejsce docelowe należy do modułu funkcji o określonej przez Ciebie nazwie.

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

Gdy dotrzesz do jednego z tych miejsc docelowych, biblioteka Dynamic Navigator najpierw sprawdza, czy moduł funkcji jest zainstalowany. Jeśli moduł funkcji jest już dostępny, aplikacja przejdzie do miejsca docelowego zgodnie z oczekiwaniami. Jeśli go nie ma, podczas instalowania modułu będzie się wyświetlał pośredni fragment postępu. Domyślna implementacja fragmentu postępu wyświetla podstawowy interfejs z paskiem postępu i obsługuje wszelkie błędy instalacji.

2 ekrany wczytywania, które przy pierwszym przejściu do modułu funkcji wyświetlają pasek postępu.
Rysunek 1. Interfejs z paskiem postępu, gdy użytkownik po raz pierwszy otwiera funkcję na żądanie. Aplikacja wyświetli ten ekran jako odpowiedni moduł do pobrania.

Aby dostosować ten interfejs lub ręcznie obsługiwać postęp instalacji z poziomu ekranu własnej aplikacji, przeczytaj sekcje Dostosowywanie fragmentu postępu i Monitorowanie stanu żądania w tym temacie.

Strony docelowe, które nie mają zdefiniowanego atrybutu app:moduleName, nadal będą działać bez zmian i zachować się tak, jakby aplikacja używała zwykłego interfejsu NavHostFragment.

Dostosuj fragment postępu

Aby zastąpić implementację fragmentu postępu w przypadku każdego wykresu nawigacyjnego, ustaw atrybut app:progressDestination na identyfikator miejsca docelowego, którego chcesz używać do obsługi postępu instalacji. Niestandardowe miejsce docelowe postępu powinno mieć postać Fragment i pochodzi z parametru AbstractProgressFragment. Musisz zastąpić metody abstrakcyjne w przypadku powiadomień o postępie instalacji, błędach i innych zdarzeniach. Postęp instalacji możesz pokazać w wybranym interfejsie.

Klasa DefaultProgressFragment implementacji domyślnej używa tego interfejsu API do wyświetlania postępu instalacji.

Monitorowanie stanu żądania

Biblioteka Dynamic Navigator umożliwia wdrożenie przepływu UX podobnego do opisanego w sprawdzonych metodach dotyczących UX na żądanie. Polega on na tym, że przed zakończeniem instalacji użytkownik pozostaje w kontekście poprzedniego ekranu. Oznacza to, że nie musisz w ogóle wyświetlać pośredniego interfejsu ani fragmentu postępu.

ekran z dolnym paskiem nawigacyjnym z ikoną wskazującą, że trwa pobieranie modułu funkcji
Rysunek 2. Ekran z widocznym postępem pobierania na dolnym pasku nawigacyjnym.

W takim przypadku odpowiadasz za monitorowanie i obsługę wszystkich stanów instalacji, zmian postępu, błędów itp.

Aby zainicjować ten nieblokujący proces nawigacji, przekaż obiekt DynamicExtras zawierający obiekt DynamicInstallMonitor do NavController.navigate(), jak pokazano w tym przykładzie:

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

Bezpośrednio po wywołaniu metody navigate() sprawdź wartość installMonitor.isInstallRequired, aby zobaczyć, czy próba nawigacji spowodowała instalację modułu funkcji.

  • Jeśli wartość to false, przejdziesz do normalnego miejsca docelowego i nie musisz nic więcej robić.
  • Jeśli wartość to true, zacznij obserwować obiekt LiveData, który jest teraz w lokalizacji installMonitor.status. Ten obiekt LiveData emituje aktualizacje SplitInstallSessionState z biblioteki Play Core. Te aktualizacje zawierają zdarzenia postępu instalacji, które można wykorzystać do aktualizacji interfejsu użytkownika. Pamiętaj o obsłudze wszystkich istotnych stanów zgodnie z opisem w przewodniku po Google Play Core, w tym także z prośbą o potwierdzenie przez użytkownika, jeśli to konieczne.

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

Po zakończeniu instalacji obiekt LiveData wyda stan SplitInstallSessionStatus.INSTALLED. Następnie należy ponownie zadzwonić pod numer NavController.navigate(). Moduł został zainstalowany, więc wywołanie się uda, a aplikacja przejdzie do miejsca docelowego zgodnie z oczekiwaniami.

Po osiągnięciu stanu terminala, na przykład po zakończeniu instalacji lub niepowodzeniu instalacji, usuń obserwatora LiveData, aby uniknąć wycieku pamięci. Aby sprawdzić, czy stan wskazuje na stan terminala, użyj funkcji SplitInstallSessionStatus.hasTerminalStatus().

Przykład implementacji obserwatora znajdziesz w sekcji AbstractProgressFragment.

Dołączone wykresy

Biblioteka dynamicznej nawigacji obsługuje dołączanie wykresów zdefiniowanych w modułach funkcji. Aby uwzględnić wykres zdefiniowany w module funkcji, wykonaj te czynności:

  1. Użyj właściwości <include-dynamic/> zamiast <include/>, jak pokazano w tym przykładzie:

    <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. W elemencie <include-dynamic ... /> musisz określić te atrybuty:

    • app:graphResName: nazwa pliku zasobów wykresu nawigacyjnego. Nazwa pochodzi z nazwy pliku wykresu. Jeśli na przykład wykres przedstawia obszar res/navigation/nav_graph.xml, nazwa zasobu to nav_graph.
    • android:id – identyfikator miejsca docelowego wykresu. Biblioteka Dynamic Navigator ignoruje wszystkie wartości android:id, które znajdują się w elemencie głównym dołączonego wykresu.
    • app:moduleName: nazwa pakietu modułu.

Użyj właściwego wykresu Package

Ważne jest, aby app:graphPackage był poprawny, ponieważ w przeciwnym razie komponent Nawigacja nie będzie mógł uwzględnić określonego navGraph z modułu funkcji.

Nazwa pakietu modułu funkcji dynamicznych tworzy się przez dołączenie nazwy modułu do elementu applicationId podstawowego modułu aplikacji. Jeśli więc moduł aplikacji podstawowej ma applicationId o wartości com.example.dynamicfeatureapp, a moduł funkcji dynamicznych o nazwie DynamicFeatureModule, nazwa pakietu modułu dynamicznego będzie wyglądać tak: com.example.dynamicfeatureapp.DynamicFeatureModule. Wielkość liter w nazwie pakietu jest rozróżniana.

Jeśli masz wątpliwości, możesz potwierdzić nazwę pakietu modułu funkcji, sprawdzając wygenerowany AndroidManifest.xml. Po utworzeniu projektu przejdź do usługi <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml, która powinna wyglądać mniej więcej tak:

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

Wartość featureSplit powinna być zgodna z nazwą modułu funkcji dynamicznych, a pakiet będzie zgodny z parametrem applicationId podstawowego modułu aplikacji. app:graphPackage to kombinacja tych elementów: com.example.dynamicfeatureapp.DynamicFeatureModule.

Możesz przejść tylko do obszaru startDestination na wykresie nawigacyjnym include-dynamic. Moduł dynamiczny odpowiada za własny wykres nawigacyjny, o którym aplikacja podstawowa nie wie.

Mechanizm uwzględniania-dynamiczny umożliwia dołączenie do podstawowego modułu aplikacji zagnieżdżonego wykresu nawigacyjnego zdefiniowanego w module dynamicznym. Ten zagnieżdżony wykres nawigacyjny działa jak każdy zagnieżdżony wykres nawigacyjny. Główny wykres nawigacyjny (tj. element nadrzędny zagnieżdżonego wykresu) może wskazywać tylko zagnieżdżony wykres nawigacyjny jako miejsce docelowe, a nie jego elementy podrzędne. Dlatego element startDestination jest używany, gdy miejscem docelowym jest wykres nawigacji dynamicznej.

Ograniczenia

  • Wykresy dynamiczne nie obsługują obecnie precyzyjnych linków.
  • Dynamicznie wczytywane wykresy zagnieżdżone (czyli element <navigation> z elementem app:moduleName) nie obsługują obecnie precyzyjnych linków.