Best Practices für die Navigation bei Projekten mit mehreren Modulen

Eine Navigationsgrafik kann aus einer beliebigen Kombination der folgenden Elemente bestehen:

  • Ein einzelnes Ziel, z. B. ein <fragment>-Ziel.
  • Ein verschachtelter Graph, der eine Reihe zusammengehöriger Ziele einschließt.
  • Ein <include>-Element, mit dem Sie eine andere Navigationsgrafikdatei so einbetten können, als wäre sie verschachtelt.

Mit dieser Flexibilität können Sie kleinere Navigationsdiagramme kombinieren, um die vollständige Navigationsgrafik Ihrer App zu bilden, auch wenn diese kleineren Navigationsdiagramme über separate Module bereitgestellt werden.

In den Beispielen in diesem Thema konzentriert sich jedes Funktionsmodul auf ein Feature und stellt eine einzelne Navigationsgrafik bereit, die alle Ziele enthält, die zur Implementierung dieses Features erforderlich sind. In einer Produktions-App gibt es möglicherweise viele untergeordnete Module auf einer niedrigeren Ebene, bei denen es sich um Implementierungsdetails dieses übergeordneten Funktionsmoduls handelt. Alle diese Funktionsmodule sind entweder direkt oder indirekt in Ihrem app-Modul enthalten. Die in diesem Dokument verwendete Beispiel-Anwendung mit mehreren Modulen hat die folgende Struktur:

Abhängigkeitsdiagramm für eine Beispielanwendung mit mehreren Modulen
Startziel der Beispiel-App
Abbildung 1. Anwendungsarchitektur und Startziel für die Beispielanwendung.

Jedes Funktionsmodul ist eine eigenständige Einheit mit einer eigenen Navigationsgrafik und eigenen Zielen. Das Modul app ist von jedem Modul abhängig und fügt es wie hier gezeigt als Implementierungsdetails in der Datei build.gradle hinzu:

Groovig

dependencies {
    ...
    implementation project(":feature:home")
    implementation project(":feature:favorites")
    implementation project(":feature:settings")

Kotlin

dependencies {
    ...
    implementation(project(":feature:home"))
    implementation(project(":feature:favorites"))
    implementation(project(":feature:settings"))

Die Rolle des app-Moduls

Das Modul app stellt das vollständige Diagramm für Ihre Anwendung bereit und fügt das NavHost in Ihre UI ein. Im Navigationsdiagramm des Moduls app können Sie mithilfe von <include> auf die Bibliotheksdiagramme verweisen. Die Verwendung von <include> funktioniert funktional genauso wie eine verschachtelte Grafik. <include> unterstützt jedoch Grafiken aus anderen Projektmodulen oder Bibliotheksprojekten, wie im folgenden Beispiel gezeigt:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_nav_graph">

    <include app:graph="@navigation/home_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />
</navigation>

Sobald eine Bibliothek in die Navigationsgrafik auf oberster Ebene aufgenommen wurde, können Sie nach Bedarf zu den Bibliotheksdiagrammen navigieren. Sie können beispielsweise eine Aktion erstellen, um von einem Fragment in der Navigationsgrafik zum Einstellungsdiagramm zu wechseln:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_nav_graph">

    <include app:graph="@navigation/home_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />

    <fragment
        android:id="@+id/random_fragment"
        android:name="com.example.android.RandomFragment"
        android:label="@string/fragment_random" >
        <!-- Launch into Settings Navigation Graph -->
        <action
            android:id="@+id/action_random_fragment_to_settings_nav_graph"
            app:destination="@id/settings_nav_graph" />
    </fragment>
</navigation>

Wenn mehrere Featuremodule auf einen gemeinsamen Satz von Zielen verweisen müssen, z. B. eine Anmeldegrafik, sollten Sie diese gemeinsamen Ziele nicht in die Navigationsgrafik der einzelnen Featuremodule aufnehmen. Stattdessen können Sie diese häufigen Ziele dem Navigationsdiagramm des app-Moduls hinzufügen. Jedes Featuremodul kann dann durch die Featuremodule navigieren, um zu diesen gängigen Zielen zu gelangen.

Im vorherigen Beispiel gibt die Aktion das Navigationsziel @id/settings_nav_graph an. Diese ID bezieht sich auf ein Ziel, das im enthaltenen Diagramm @navigation/settings_navigation. definiert ist

Navigation der obersten Ebene im App-Modul

Die Navigationskomponente enthält eine NavigationUI-Klasse. Diese Klasse enthält statische Methoden, die die Navigation über die obere App-Leiste, die Navigationsleiste und die Navigation am unteren Rand verwalten. Wenn die Ziele Ihrer App auf oberster Ebene aus UI-Elementen bestehen, die von Funktionsmodulen bereitgestellt werden, ist das Modul app ein idealer Ort, um die Navigations- und UI-Elemente der obersten Ebene zu platzieren. Da das Anwendungsmodul von den gemeinsam verwendeten Funktionsmodulen abhängt, können Sie über Code, der in Ihrem Anwendungsmodul definiert wurde, auf alle Ziele zugreifen. Das bedeutet, dass Sie NavigationUI verwenden können, um Ziele mit Menüpunkten zu verknüpfen, wenn die ID des Elements mit der ID eines Ziels übereinstimmt.

In Abbildung 2 definiert das Beispielmodul app ein BottomNavigationView in seiner Hauptaktivität. Die Menüelement-IDs im Menü stimmen mit den Navigationsdiagramm-IDs der Bibliotheksgrafiken überein:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/home_nav_graph"
        android:icon="@drawable/ic_home"
        android:title="Home"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/favorites_nav_graph"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/settings_nav_graph"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="ifRoom" />
</menu>

Damit NavigationUI die Navigation unten verarbeiten kann, rufen Sie setupWithNavController() von onCreate() in Ihrer Hauptaktivitätsklasse auf, wie im folgenden Beispiel gezeigt:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    NavHostFragment navHostFragment =
            (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);

    NavigationUI.setupWithNavController(bottomNav, navController);
}

Mit diesem Code ruft NavigationUI das entsprechende Bibliotheksdiagramm auf, wenn der Nutzer auf ein unteres Navigationselement klickt.

Es ist generell nicht empfehlenswert, wenn Ihr App-Modul eine starke Abhängigkeit von einem bestimmten Ziel hat, das tief in die Navigationsgrafik Ihrer Feature-Module eingebettet ist. In den meisten Fällen sollte Ihr Anwendungsmodul nur den Einstiegspunkt für eingebettete oder enthaltene Navigationsdiagramme kennen (dies gilt auch außerhalb von Funktionsmodulen). Wenn Sie auf ein Ziel verlinken müssen, das tief in der Navigationsgrafik Ihrer Bibliothek enthalten ist, verwenden Sie am besten einen Deeplink. Deeplinks sind außerdem die einzige Möglichkeit für eine Bibliothek, zu einem Ziel in der Navigationsgrafik einer anderen Bibliothek zu gelangen.

Zwischen Funktionsmodule wechseln

Zum Zeitpunkt der Kompilierung können unabhängige Featuremodule einander nicht sehen. Daher können Sie IDs nicht verwenden, um zu Zielen in anderen Modulen zu navigieren. Verwenden Sie stattdessen einen Deeplink, um direkt zu einem Ziel zu gelangen, das mit einem impliziten Deeplink verknüpft ist.

Ausgehend vom vorherigen Beispiel stellen Sie sich vor, Sie müssen von einer Schaltfläche im Modul :feature:home zu einem Ziel navigieren, das im Modul :feature:settings verschachtelt ist. Fügen Sie dazu in der Navigationsgrafik für die Einstellungen einen Deeplink zum Ziel hinzu, wie hier gezeigt:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/settings_nav_graph"
    app:startDestination="@id/settings_fragment_one">

    ...

    <fragment
        android:id="@+id/settings_fragment_two"
        android:name="com.example.google.login.SettingsFragmentTwo"
        android:label="@string/settings_fragment_two" >

        <deepLink
            app:uri="android-app://example.google.app/settings_fragment_two" />
    </fragment>
</navigation>

Fügen Sie dann den folgenden Code in das onClickListener der Schaltfläche im Home-Fragment ein:

Kotlin

button.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("android-app://example.google.app/settings_fragment_two".toUri())
        .build()
    findNavController().navigate(request)
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        NavDeepLinkRequest request = NavDeepLinkRequest.Builder
            .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two"))
            .build();
        NavHostFragment.findNavController(this).navigate(request);
    }
});

Im Gegensatz zur Navigation mithilfe von Aktions- oder Ziel-IDs können Sie jeden URI in einer Grafik aufrufen, auch modulübergreifend.

Beim Navigieren mit URI wird der Back-Stack nicht zurückgesetzt. Dieses Verhalten unterscheidet sich von der expliziten Deeplink-Navigation, bei der beim Navigieren der Back Stack ersetzt wird.