Android-Entwicklerleitfaden für Fragment-Navigationsmuster
Veröffentlicht: 2022-03-11Im Laufe der Jahre habe ich viele verschiedene Implementierungen von Navigationsmustern in Android gesehen. Einige der Apps verwendeten nur Aktivitäten, während andere Aktivitäten mit Fragmenten und/oder mit benutzerdefinierten Ansichten gemischt waren.
Eine meiner bevorzugten Implementierungen von Navigationsmustern basiert auf der „One-Activity-Multiple-Fragments“-Philosophie oder einfach dem Fragment-Navigationsmuster, bei dem jeder Bildschirm in der Anwendung ein Vollbild-Fragment ist und alle oder die meisten dieser Fragmente darin enthalten sind eine Aktivität.
Dieser Ansatz vereinfacht nicht nur die Implementierung der Navigation, sondern hat auch eine viel bessere Leistung und bietet folglich eine bessere Benutzererfahrung.
In diesem Artikel werden wir uns einige gängige Implementierungen von Navigationsmustern in Android ansehen und dann das fragmentbasierte Navigationsmuster vorstellen, indem wir es mit den anderen vergleichen und kontrastieren. Eine Demoanwendung, die dieses Muster implementiert, wurde auf GitHub hochgeladen.
Welt der Aktivitäten
Eine typische Android-Anwendung, die nur Aktivitäten verwendet, ist in einer baumartigen Struktur organisiert (genauer gesagt in einem gerichteten Graphen), wo die Root-Aktivität durch den Launcher gestartet wird. Während Sie in der Anwendung navigieren, gibt es einen Aktivitäts-Backstack, der vom Betriebssystem verwaltet wird.
Ein einfaches Beispiel ist im folgenden Diagramm dargestellt:
Aktivität A1 ist der Einstiegspunkt in unsere Anwendung (stellt beispielsweise einen Begrüßungsbildschirm oder ein Hauptmenü dar) und von dort aus kann der Benutzer zu A2 oder A3 navigieren. Wenn Sie zwischen Aktivitäten kommunizieren müssen, können Sie startActivityForResult() verwenden oder vielleicht teilen Sie ein global zugängliches Geschäftslogikobjekt zwischen ihnen.
Wenn Sie eine neue Aktivität hinzufügen müssen, müssen Sie die folgenden Schritte ausführen:
- Definieren Sie die neue Aktivität
- Registrieren Sie es in der AndroidManifest.xml
- Öffnen Sie es mit einer startActivity() aus einer anderen Aktivität
Natürlich ist dieses Navigationsdiagramm ein ziemlich vereinfachter Ansatz. Es kann sehr komplex werden, wenn Sie den Backstack manipulieren müssen oder wenn Sie dieselbe Aktivität mehrmals wiederverwenden müssen, z. B. wenn Sie den Benutzer durch einige Tutorial-Bildschirme navigieren möchten, aber jeder Bildschirm tatsächlich dieselbe Aktivität als verwendet Base.
Glücklicherweise haben wir dafür Tools namens Tasks und einige Richtlinien für die richtige Backstack-Navigation.
Dann kamen mit API Level 11 Fragmente…
Welt der Fragmente
Android hat Fragmente in Android 3.0 (API-Level 11) eingeführt, hauptsächlich um dynamischere und flexiblere UI-Designs auf großen Bildschirmen wie Tablets zu unterstützen. Da der Bildschirm eines Tablets viel größer ist als der eines Mobilteils, gibt es mehr Platz zum Kombinieren und Austauschen von UI-Komponenten. Fragmente ermöglichen solche Designs, ohne dass Sie komplexe Änderungen an der Ansichtshierarchie verwalten müssen. Indem Sie das Layout einer Aktivität in Fragmente unterteilen, können Sie das Erscheinungsbild der Aktivität zur Laufzeit ändern und diese Änderungen in einem von der Aktivität verwalteten Backstack beibehalten. – zitiert aus dem API-Leitfaden von Google für Fragmente.
Dieses neue Spielzeug ermöglichte es Entwicklern, eine Benutzeroberfläche mit mehreren Fenstern zu erstellen und die Komponenten in anderen Aktivitäten wiederzuverwenden. Manche Entwickler lieben das, andere nicht. Es ist eine beliebte Debatte, ob Fragmente verwendet werden sollen oder nicht, aber ich denke, jeder würde zustimmen, dass Fragmente zusätzliche Komplexität mit sich bringen und die Entwickler sie wirklich verstehen müssen, um sie richtig zu verwenden.
Vollbild-Fragment Nightmare in Android
Ich sah immer mehr Beispiele, bei denen die Fragmente nicht nur einen Teil des Bildschirms darstellten, sondern tatsächlich der gesamte Bildschirm ein Fragment war, das in einer Aktivität enthalten war. Einmal habe ich sogar ein Design gesehen, bei dem jede Aktivität genau ein Vollbildfragment und nichts mehr hatte und der einzige Grund, warum diese Aktivitäten existierten, darin bestand, diese Fragmente zu hosten. Neben dem offensichtlichen Designfehler gibt es noch ein weiteres Problem bei diesem Ansatz. Schauen Sie sich das Diagramm von unten an:
Wie kann A1 mit F1 kommunizieren? Nun, A1 hat die totale Kontrolle über F1, seit es F1 erstellt hat. A1 kann beispielsweise bei der Erstellung von F1 ein Bündel übergeben oder seine öffentlichen Methoden aufrufen. Wie kann F1 mit A1 kommunizieren? Nun, das ist komplizierter, aber es kann mit einem Rückruf/Beobachter-Muster gelöst werden, bei dem A1 F1 abonniert und F1 A1 benachrichtigt.
Aber wie können A1 und A2 miteinander kommunizieren? Dies wurde bereits behandelt, beispielsweise über startActivityForResult() .
Und jetzt kommt die eigentliche Frage: Wie können F1 und F2 miteinander kommunizieren? Selbst in diesem Fall können wir eine global verfügbare Geschäftslogikkomponente haben, sodass sie zum Übergeben von Daten verwendet werden kann. Doch nicht immer führt dies zu elegantem Design. Was ist, wenn F2 einige Daten direkter an F1 weitergeben muss? Nun, mit einem Rückrufmuster kann F2 A2 benachrichtigen, dann endet A2 mit einem Ergebnis und dieses Ergebnis wird von A1 erfasst, das F1 benachrichtigt.
Dieser Ansatz erfordert viel Boilerplate-Code und wird schnell zu einer Quelle von Fehlern, Schmerzen und Ärger.
Was wäre, wenn wir alle Aktivitäten loswerden und nur eine davon behalten könnten, die den Rest der Fragmente behält?
Fragment-Navigationsmuster
Im Laufe der Jahre habe ich begonnen, das Muster „Eine Aktivität – Mehrere Fragmente“ in den meisten meiner Anwendungen zu verwenden, und ich verwende es immer noch. Es gibt viele Diskussionen über diesen Ansatz, zum Beispiel hier und hier. Was ich jedoch vermisst habe, ist ein konkretes Beispiel, das ich selbst sehen und testen kann.
Schauen wir uns das folgende Diagramm an:
Jetzt haben wir nur noch eine Containeraktivität und mehrere Fragmente, die wiederum eine baumartige Struktur haben. Die Navigation zwischen ihnen wird vom FragmentManager übernommen , er hat seinen Backstack.
Beachten Sie, dass wir jetzt kein startActivityForResult() haben, aber wir können ein Callback/Observer-Muster implementieren. Sehen wir uns einige Vor- und Nachteile dieses Ansatzes an:
Vorteile:
1. Sauberere und wartungsfreundlichere AndroidManifest.xml
Da wir jetzt nur noch eine Aktivität haben, müssen wir das Manifest nicht mehr jedes Mal aktualisieren, wenn wir einen neuen Bildschirm hinzufügen. Im Gegensatz zu Aktivitäten müssen wir keine Fragmente deklarieren.
Dies mag wie eine Kleinigkeit erscheinen, aber für größere Anwendungen mit mehr als 50 Aktivitäten kann dies die Lesbarkeit der AndroidManifest.xml -Datei erheblich verbessern.

Sehen Sie sich die Manifestdatei der Beispielanwendung an, die mehrere Bildschirme hat. Die Manifestdatei bleibt immer noch super einfach.
<?xml version="1.0" encoding="utf-8"?> package="com.exarlabs.android.fragmentnavigationdemo.ui" > <application android:name= ".FragmentNavigationDemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
2. Zentralisiertes Navigationsmanagement
In meinem Codebeispiel sehen Sie, dass ich NavigationManager verwende, der in meinem Fall in jedes Fragment eingefügt wird. Dieser Manager kann als zentraler Ort für die Protokollierung, Backstack-Verwaltung usw. verwendet werden, sodass das Navigationsverhalten von der restlichen Geschäftslogik entkoppelt und nicht in Implementierungen verschiedener Bildschirme verteilt wird.
Stellen wir uns eine Situation vor, in der wir einen Bildschirm starten möchten, auf dem der Benutzer einige Elemente aus einer Personenliste auswählen kann. Sie möchten auch einige Filterargumente wie Alter und Beruf und Geschlecht übergeben.
Im Fall von Aktivitäten würden Sie schreiben:
Intent intent = new Intent(); intent.putExtra("age", 40); intent.putExtra("occupation", "developer"); intent.putExtra("gender", "female"); startActivityForResult(intent, 100);
Dann müssen Sie das onActivityResult irgendwo unten definieren und das Ergebnis behandeln.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); }
Mein persönliches Problem bei diesem Ansatz ist, dass diese Argumente „Extras“ sind und nicht obligatorisch sind, also muss ich sicherstellen, dass die empfangende Aktivität all die verschiedenen Fälle behandelt, in denen ein Extra fehlt. Später, wenn ein Refactoring durchgeführt wird und beispielsweise das Extra „Alter“ nicht mehr benötigt wird, muss ich überall im Code suchen, wo ich diese Aktivität beginne, und sicherstellen, dass alle Extras korrekt sind.
Außerdem wäre es nicht schöner, wenn das Ergebnis (Personenliste) in Form von _List ankommen würde
Bei fragmentbasierter Navigation ist alles einfacher. Sie müssen lediglich eine Methode namens startPersonSelectorFragment() mit den erforderlichen Argumenten und einer Callback-Implementierung in den NavigationManager schreiben.
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() { @Override public boolean onPersonsSelected(List<Person> selection) { [do something] return false; } });
Oder mit RetroLambda
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);
3. Bessere Kommunikationsmittel zwischen Bildschirmen
Zwischen Aktivitäten können wir nur ein Bundle teilen, das Primitive oder serialisierte Daten enthalten kann. Jetzt können wir mit Fragmenten ein Callback-Muster implementieren, bei dem F1 beispielsweise auf F2 hören kann, das beliebige Objekte übergibt. Sehen Sie sich bitte die Callback-Implementierung der vorherigen Beispiele an, die eine _List zurückgibt
4. Baufragmente sind günstiger als Bauaktivitäten
Dies wird deutlich, wenn Sie eine Schublade verwenden, die beispielsweise 5 Menüpunkte hat und auf jeder Seite die Schublade erneut angezeigt werden soll.
Bei reiner Aktivitätsnavigation sollte jede Seite die Schublade aufblasen und initialisieren, was natürlich teuer ist.
Auf dem Diagramm unten sehen Sie mehrere Stammfragmente (FR*), die Vollbildfragmente sind, auf die direkt aus der Schublade zugegriffen werden kann, und die Schublade ist auch nur zugänglich, wenn diese Fragmente angezeigt werden. Alles, was sich rechts von der gestrichelten Linie im Diagramm befindet, dient als Beispiel für ein beliebiges Navigationsschema.
Da die Container-Aktivität die Schublade enthält, haben wir nur eine Schubladeninstanz, sodass Sie sie nicht bei jedem Navigationsschritt, bei dem die Schublade sichtbar sein soll, erneut aufblasen und initialisieren müssen. Immer noch nicht überzeugt, wie all dies funktioniert? Schauen Sie sich meine Beispielanwendung an, die die Verwendung von Schubladen demonstriert.
Nachteile
Meine größte Befürchtung war immer, dass ich bei Verwendung von fragmentbasierten Navigationsmustern in einem Projekt irgendwann auf ein unvorhergesehenes Problem stoßen würde, das aufgrund der zusätzlichen Komplexität von Fragmenten, Bibliotheken von Drittanbietern und den verschiedenen Betriebssystemversionen schwer zu lösen wäre. Was wäre, wenn ich alles, was ich bisher getan habe, umgestalten müsste?
Tatsächlich musste ich Probleme mit verschachtelten Fragmenten lösen, Bibliotheken von Drittanbietern, die auch Fragmente wie ShinobiControls, ViewPagers und FragmentStatePagerAdapters verwenden.
Ich muss zugeben, dass es ein ziemlich langer Prozess war, genügend Erfahrung mit Fragmenten zu sammeln, um diese Probleme lösen zu können. Aber in jedem Fall ging es nicht darum, dass die Philosophie schlecht ist, sondern dass ich Fragmente nicht gut genug verstanden habe. Wenn Sie Fragmente besser verstehen als ich, würden Sie vielleicht nicht einmal auf diese Probleme stoßen.
Der einzige Nachteil, den ich jetzt erwähnen kann, ist, dass wir immer noch auf Probleme stoßen können, die nicht trivial zu lösen wären, da es keine ausgereifte Bibliothek gibt, die alle komplexen Szenarien einer komplexen Anwendung mit fragmentbasierter Navigation zeigt.
Fazit
In diesem Artikel haben wir eine alternative Möglichkeit zur Implementierung der Navigation in einer Android-Anwendung gesehen. Wir haben es mit der traditionellen Navigationsphilosophie verglichen, die Aktivitäten verwendet, und wir haben einige gute Gründe gesehen, warum es vorteilhafter ist, es gegenüber dem traditionellen Ansatz zu verwenden.
Falls Sie dies noch nicht getan haben, sehen Sie sich die auf GitHub hochgeladene Demoanwendung für die Implementierung an. Fühlen Sie sich frei, mit schöneren Beispielen zu forken oder dazu beizutragen, die seine Verwendung besser zeigen würden.