
Fast jedes wachsende Projekt beginnt früher oder später, sich mit multimodularer Architektur zu befassen. Entwickler möchten nicht warten, bis das gesamte Projekt wieder zusammengesetzt ist, wenn nur ein Feature geändert wurde. Multimodularität hilft, Anwendungsfunktionen voneinander zu isolieren, wodurch die Erstellungszeit verkürzt wird. Diese Isolierung schränkt jedoch den Umfang der Komponenten ein. Wenn Sie die Navigation von Jetpack in einem Projekt mit einem Modul verwenden, ist das Navigationsdiagramm in jedem Anwendungspaket verfügbar. Sie können jederzeit explizit angeben, welche Aktion NavController ausführen soll, und Sie können auch auf den globalen Host zugreifen, wenn das Projekt verschachtelte Fragmente enthält. Bei vielen Modulen stellt sich jedoch die Frage, wo das Navigationsdiagramm erstellt werden soll, wie darauf zugegriffen werden kann und wie die Abhängigkeiten der Module nicht verwechselt werden können. Wir werden im Folgenden über all dies sprechen.
Navigationsdiagramm
Das Wichtigste beim Entwerfen einer Anwendung mit mehreren Modulen sind Abhängigkeiten. Abhängigkeiten im Modul-Abhängigkeitsbaum sollten in eine Richtung gerichtet sein.

Das am meisten abhängige Modul in einer Multimodul-Anwendung ist immer das App-Modul. Er kennt fast alle anderen Module. In der App werden DIs normalerweise mit verschiedenen Frameworks implementiert. Mit dieser Abhängigkeit des App-Moduls können Sie das Navigationsdiagramm des Haupthosts darin implementieren.
Sie sollten immer daran denken, dass das App-Modul so wenig Funktionalität wie möglich implementieren sollte, da dies am abhängigsten ist und fast jede Änderung im Projekt zum erneuten Zusammenbau des App-Moduls führt.
Sprechen ist billig. Zeig mir den Code
Und gleich zu einem realen Beispiel. Unser Fall: Der Einstiegspunkt in die Anwendung ist der Begrüßungsbildschirm. Er bestimmt, zu welchem Bildschirm weitergegangen wird: zur Hauptfunktionalität oder Autorisierung. Vom Autorisierungsbildschirm aus erfolgt ein Übergang nur zur Hauptfunktionalität. Wie gewohnt erstellen wir eine Navigationsgrafik - nichts kompliziertes.

Zugriff auf die Navigation innerhalb des Moduls
Wenn es an der Zeit ist, von einem Bildschirm zu einem Bildschirm in einem anderen Modul zu wechseln, stellt sich die Frage: Wie?
Tatsächlich gibt es im Funktionsmodul keinen Zugriff auf das Navigationsdiagramm, um die Aktions-ID abzurufen, die der NavController ausführen muss.
Dies wird gelöst, indem DI über Schnittstellen implementiert wird. Anstelle des Funktionsmoduls, das von der globalen Navigationsgrafik des App-Moduls abhängt, erstellen wir eine Schnittstelle und nennen sie WhatToNavCommandProvider, deren Variablen Navigationsbefehle sind.
SplashNavCommandProvider.kt
interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand }
Die Schnittstelle des Befehlsanbieters wird im App-Modul implementiert, und die Klasse des Navigationsbefehls enthält dieselben Felder wie die Argumente für die NavController.navigate-Methode
NavCommand.kt
data class NavCommand( val action: Int, var args: Bundle? = null, val navOptions: NavOptions? = null )
Mal sehen, wie es in der Praxis aussieht. Vom Begrüßungsbildschirm sind 2 Übergänge möglich: zum Autorisierungsbildschirm und zum Hauptfunktionsbildschirm. Erstellen Sie im Begrüßungsmodul eine Schnittstelle:
SplashNavCommandProvider.kt
interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand }
Im App-Modul erstellen wir eine Implementierung dieser Schnittstelle und stellen sie mit dem di-Framework (I have Dagger) über die Splash-Schnittstelle dem Modul zur Verfügung.
SplashNavCommandProviderImpl.kt - Implementierung von CommandProvider
class SplashNavCommandProviderImpl @Inject constructor() : SplashNavCommandProvider { override val toAuth: NavCommand = NavCommand(R.id.action_splashFragment_to_authFragment) override val toMain: NavCommand = NavCommand(R.id.action_splashFragment_to_mainFragment) }
SplashNavigationModule.kt - DI-Modul zum Bereitstellen von Abhängigkeiten
@Module interface SplashNavigationModule { @Binds fun bindSplashNavigator(impl: SplashNavCommandProviderImpl): SplashNavCommandProvider }
AppActivityModule.kt - Das DI-Hauptmodul der Anwendung
@Module interface AppActivityModule { @FragmentScope @ContributesAndroidInjector( modules = [ SplashNavigationModule::class ] ) fun splashFragmentInjector(): SplashFragment … }
Im Splash-Modul implementieren wir die Implementierung in MV (hier) entweder Presenter oder ViewModel ...
SplashViewModel.kt
class SplashViewModel @Inject constructor( private val splashNavCommandProvider: SplashNavCommandProvider ) ...
Wenn die Logik des Bildschirms berücksichtigt, dass es Zeit ist, zu einem anderen Bildschirm zu wechseln, übertragen wir einen Befehl an unser Fragment und informieren, dass wir zu einem anderen Bildschirm wechseln müssen.
Es wäre möglich, die Implementierung von SplashNavCommandProvider direkt in das Fragment zu implementieren, aber dann verlieren wir die Fähigkeit, die Navigation zu testen.
Um den Übergang im Fragment selbst abzuschließen, müssen Sie NavController herunterladen. Wenn der aktuelle Bildschirm kein verschachteltes Fragment ist, rufen wir einfach den NavController mit der findNavController () -Methode ab und rufen die navigate -Methode auf:
findNavController().navigate(toMain)
Sie können es etwas bequemer machen, indem Sie eine Erweiterung für das Fragment schreiben
Framentmentxt.kt
fun Fragment.navigate(navCommand: NavCommand) { findNavController().navigate(navCommand.action, navCommand.args, navCommand.navOptions) }
Warum gerade für das Fragment? Da ich den SingleActivity-Ansatz verwende, können Sie bei mehreren Erweiterungen auch für Activity erstellen.
Dann sieht die Navigation innerhalb des Fragments so aus
navigate(toMain)
Verschachtelte Fragmente
Es gibt zwei Arten der Navigation in verschachtelten Fragmenten:
- Übergang in einem verschachtelten Container
- In den Container gehen ein oder mehrere Level höher. Zum Beispiel ein globaler Aktivitätshost
Im ersten Fall ist alles einfach, die Erweiterung, die wir oben geschrieben haben, ist für uns geeignet. Und um den Übergang im zweiten Fall abzuschließen, müssen Sie den NavController des gewünschten Hosts herunterladen. Dazu müssen Sie die ID dieses Hosts im Modul abrufen. Da nur das Modul, das das Navigationsdiagramm dieses Hosts implementiert, Zugriff darauf hat, erstellen wir eine Abhängigkeit und implementieren sie in den Funktionsmodulen, in denen der Zugriff auf einen bestimmten NavController erforderlich ist, über Dagger.
GlobalHostModule.kt - DI-Modul zur Bereitstellung globaler Host-ID-Abhängigkeiten
@Provides @GlobalHost fun provideGlobalHostId(): Int = R.id.host_global
AppActivityModule.kt - Das DI-Hauptmodul der Anwendung
@FragmentScope @ContributesAndroidInjector( modules = [ GlobalHostModule::class, ProfileNavigationModule::class, ... ] ) fun profileKnownFragmentInjector(): ProfileKnownFragment
Einbetten der Host-ID-Abhängigkeit in ein Fragment
@Inject @GlobalHost var hostId = 0
Wenn Fragmente verschachtelt sind, empfiehlt es sich, für jeden Host ein Qualifikationsmerkmal zu erstellen oder das vorhandene Qualifikationsmerkmal mit dem Namen zu verwenden, damit Dagger versteht, welches int bereitzustellen ist.
Globalhost.kt
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalHost
Nachdem die Abhängigkeits-ID des gewünschten Hosts im Fragment ermittelt wurde, können Sie den NavController anhand der Host-ID abrufen. Wir werden unsere Erweiterung verbessern, damit in jedem Container Übergänge möglich sind:
Framentmentxt.kt
fun Fragment.navigate(navCommand: NavCommand, hostId: Int? = null) { val navController = if (hostId == null) { findNavController() } else { Navigation.findNavController(requireActivity(), hostId) } navController.navigate(navCommand.action, navCommand.args, navCommand.navOptions) }
Code im Snippet
navigate(toAuth, hostId)
Dies waren die Highlights der Navigation mit Jetpack in einer Architektur mit mehreren Modulen. Wenn Sie Fragen haben, beantworte ich diese gerne in den Kommentaren :)