Mobile Anwendungen verwenden zunehmend Deep Links. Dies sind Links, mit denen Sie nicht nur von außen zur Anwendung gelangen, sondern auch zu einem bestimmten Bildschirm gelangen. Vladislav Kozhushko, ein Android-Entwickler von Yandex.Food, erklärte, warum wir die Navigation von Jetpack implementiert haben, um Deep Links zu implementieren, auf welche Probleme wir gestoßen sind, wie sie gelöst wurden und was am Ende passiert ist.
- Hallo allerseits! Ich heiße Vlad. Ich interessiere mich seit 2013 für Android-Entwicklung und arbeite seit letztem Sommer in Yandex.Ed. Ich werde Ihnen erzählen, wie wir die Bibliothek der Navigationskomponenten in eine Kampfanwendung einführen können.
Alles begann damit, dass wir die technische Aufgabe hatten, die Navigation umzugestalten. Dann kamen Produktmanager zu uns und sagten, wir würden Deeplinks machen, es würde viele von ihnen geben, sie würden zu verschiedenen Bildschirmen führen.
Und hier dachten wir uns - die in Google I / O 2018 präsentierte Navigation eignet sich sehr gut für die Implementierung von Aufgaben auf Deeplinks. Wir beschließen zu sehen, was passiert. Unsere Kollegen mit iOS in Xcode verfügen über einen praktischen grafischen Editor, in dem sie mit der Maus das gesamte Layout der Bildschirme anzeigen und Übergänge zwischen den Bildschirmen festlegen können. Jetzt haben wir auch die Möglichkeit, mit der Maus Übergänge festzulegen, Links zu Bildschirmen zu entfernen und sie mit Fragmenten zu verknüpfen. Außerdem können wir Argumente auf dem Bildschirm festlegen.

Das heißt, die Argumente müssen entweder in einem UI-Editor stecken bleiben oder in XML geschrieben sein. Zusätzlich zum Wechseln zwischen Bildschirmen wurde ein Aktions-Tag angezeigt, in dem wir die ID und den Bildschirm angeben, zu dem wir wechseln müssen.

Wir haben auch den Deeplink angegeben, mit dem wir den Bildschirm öffnen möchten. In diesem Fall gibt es einen itemId-Parameter. Wenn wir einen Parameter dieses Typs übergeben und dieser zu einem Fragment führt, wird der Wert dieses Parameters an die Fragmentargumente übergeben, und wir können ihn mit dem itemId-Schlüssel abrufen und verwenden. Die Bibliothek unterstützt auch Uplinks. Wenn wir den Uplink einrichten, z. B. navdemo.ru/start/{itemId}, müssen wir uns nicht mehr um die Registrierung von http / https-Schemata zum Öffnen solcher Diplinks kümmern. Die Bibliothek wird alles für uns tun.
Lassen Sie uns nun über die Argumente sprechen. Wir haben zwei Argumente hinzugefügt, Integer und Boolean, und ihnen auch Standardwerte gegeben.

Danach haben wir beim Zusammenstellen des Fragments die NextFragmentArgs-Klasse. Es hat einen Builder, mit dem Sie die benötigten Argumente zusammenstellen und festlegen können. Es gibt auch Getter in der NextFragmentArgs-Klasse, um unsere Argumente abzurufen. Sie können NextFragmentArgs aus dem Bundle sammeln und das Bundle konvertieren, was sehr praktisch ist.

Über die Hauptfunktionen der Bibliothek. Sie hat einen praktischen UI-Editor, in dem wir die gesamte Navigation durchführen können. Wir erhalten lesbares XML-Markup, das sich auf die gleiche Weise wie Ansichten aufbläst. Und wir haben keine solchen Schmerzen wie iOS-Entwickler, als sie etwas im Grafikeditor korrigierten, und viele Dinge haben sich geändert.
Deeplinks können sowohl mit Fragmenten als auch mit Activity arbeiten. Dazu müssen Sie keine große Menge von IntentFilter im Manifest registrieren. Wir unterstützen auch Uplinks und mit Android 6 können Sie die automatische Überprüfung zur Überprüfung aktivieren. Darüber hinaus erfolgt beim Zusammenstellen von Projekten die Codegenerierung mit Argumenten für den gewünschten Bildschirm. Die Navigation unterstützt verschachtelte Diagramme und verschachtelte Navigation, sodass Sie die gesamte Navigation logisch in separate Unterkomponenten zerlegen können.

Jetzt werden wir über unseren Weg sprechen, den wir gegangen sind, und die Bibliothek vorstellen. Alles begann mit Alpha Version 3. Wir haben alles implementiert, wir haben die gesamte Navigation durch Navigationskomponenten ersetzt, alles ist super, alles funktioniert, Diplinks offen, aber es sind Probleme aufgetreten.

Das erste Problem ist eine IllegalArgumentException. Es trat in zwei Fällen auf: Die Inkonsistenz des Diagramms, da die Darstellung des Diagramms und der Fragmente auf dem Stapel desynchronisiert wurde, gab es aus diesem Grund eine Ausnahme. Das zweite Problem sind Doppelklicks. Wenn wir den ersten Klick gemacht haben, navigieren wir. Wechselt zum nächsten Bildschirm und der Status des Diagramms ändert sich. Wenn wir den zweiten Klick machen, befindet sich das Diagramm bereits in einem neuen Zustand und es wird versucht, den alten Übergang herzustellen, der nicht mehr existiert, sodass diese Ausnahme auftritt. In dieser Version wurden keine Diplinks geöffnet, deren Schema einen Punkt enthält, z. B. project.company. Dies wurde in zukünftigen Versionen der Bibliothek entschieden, und in der stabilen Version funktioniert alles gut.
Auch gemeinsam genutzte Elemente werden nicht unterstützt. Sie haben wahrscheinlich gesehen, wie Google Play funktioniert: Es gibt eine Liste von Anwendungen, Sie klicken auf die Anwendung, Ihr Bildschirm wird geöffnet und eine schöne Animation zum Verschieben des Symbols wird angezeigt. Wir haben dies auch in der Anwendung auf der Liste der Restaurants, aber wir brauchten Unterstützung für gemeinsame Elemente. Außerdem hat SafeArgs bei uns nicht funktioniert, also haben wir ohne sie gelebt.

Es war einfach, den Deeplink zu reparieren. Es war notwendig, das in der Bibliothek befindliche Schema durch ein eigenes Schema zu ersetzen, das den Punkt unterstützt. Mit Hilfe der Reflexion klopfen wir an die Klasse, ändern den Wert von Regex und alles funktioniert.

Um einen Doppelklick zu korrigieren, haben wir die folgende Methode verwendet. Wir haben Erweiterungsfunktionen, um Klicks in der Navigation festzulegen. Nachdem Sie auf eine Schaltfläche oder ein anderes Element geklickt haben, aktualisieren wir den ClickListener und führen die Navigation durch, um einen doppelten Übergang zu vermeiden. Wenn Sie RxJava in Ihrem Projekt haben, empfehle ich die Verwendung der RxBindingsgs-Bibliothek von Jake Worton, mit der Sie Ereignisse aus View in einem reaktiven Stil mit den uns zur Verfügung stehenden Operatoren verarbeiten können.

Lassen Sie uns über gemeinsame Elemente sprechen. Da sie etwas später erschienen, haben wir beschlossen, den Navigator zu beenden und ihm die Navigation hinzuzufügen. Wir sind Programmierer, warum nicht?

Die Verfeinerung war wie folgt: Wir erben unseren Navigator vom Navigator, der sich in der Bibliothek befindet. Hier wird nicht der gesamte Code vorgestellt, aber dies ist der Hauptteil, den wir fertiggestellt haben. Ich möchte darauf hinweisen, dass vor der Navigation der Status des FragmentManager überprüft wird. Wenn es gerettet wurde, verlieren wir unsere Teams. Meiner Meinung nach ist dies ein Fehler.
Wenn wir eine Fragmenttransaktion starten, erstellen wir eine Transaktion und legen alle unsere Ansichten fest, die durchsucht werden müssen. Aber die Frage ist, was für eine Klasse ist diese TransitionDestination? Dies ist unsere benutzerdefinierte Klasse, in der Ansichten festgelegt werden können. Wir erben es von Destination und erweitern die Funktionalität. Wir legen Ansichten fest und unser Ziel ist bereit, diese zu teilen.

Der nächste Teil - wir müssen die Navigation machen. Wenn wir auf die Schaltfläche klicken, suchen wir nach dem ID-Ziel und ziehen den oberen Rand des Diagramms heraus, zu dem wir gehen müssen. Danach werden wir es in unser TransitionDestination umwandeln, in dem wir Ansichten haben. Als Nächstes stellen wir alle unsere Ansichten so ein, dass Übergänge animiert werden, und führen die Navigation durch. Alles funktioniert, alles ist super. Aber dann erschien alpha06.
Dies bedeutet nicht, dass wir zwischen den Versionen gesprungen sind. Wir haben versucht, die Bibliotheken nach Bedarf zu aktualisieren, aber vielleicht sind dies die grundlegendsten Änderungen, auf die wir gestoßen sind.
Es gab Probleme mit alpha06. Da es sich um eine Alpha-Version der Bibliothek handelte, gab es ständige Änderungen im Zusammenhang mit Umbenennungsmethoden, Rückrufen und Schnittstellen, ganz zu schweigen von der Tatsache, dass Parameter zu den Methoden hinzugefügt und daraus entfernt wurden. Da wir unseren eigenen Navigator geschrieben haben, mussten wir den Bibliotheksnavigatorcode mit unserem synchronisieren, um auch Fehlerkorrekturen und neue Funktionen einzufügen.
Auch in der Bibliothek selbst hat sich das Verhalten geändert, als wir von den frühen Alpha-Versionen zu stabilen Versionen wechseln. Einige Funktionen wurden entfernt. Früher gab es ein launchDocument-Flag, das jedoch nie verwendet wurde. Dann wurde es entfernt.

Zum Beispiel gab es eine Änderung, bei der die Entwickler sagten, dass die navigateUp () -Methode, die mit DrawerLayout funktioniert, veraltet ist. Verwenden Sie eine andere, bei der die Parameter einfach ausgetauscht werden.


Unsere nächste große Migration war bei alpha11. Hier wurden die Hauptprobleme bei der Navigation bei der Arbeit mit dem Diagramm behoben. Wir haben endlich unseren dotierten Controller entfernt und alles verwendet, was im Lieferumfang enthalten war. Sichere Argumente funktionierten bei uns immer noch nicht und wir waren verärgert.
Dann wurde beta01 veröffentlicht, und in dieser Version änderte sich praktisch nichts, als sich die Navigation verhielt, aber das folgende Problem trat auf: Wenn eine bestimmte Anzahl von Bildschirmen in der Anwendung geöffnet ist, müssen wir den Stapel löschen, bevor wir unseren Diplink öffnen. Dieses Verhalten passte nicht zu uns. Sichere Argumente funktionierten immer noch nicht.

Wir haben ein Problem bei Google geschrieben, bei dem uns mitgeteilt wurde, dass alle Regeln ursprünglich konzipiert wurden. Im Code selbst war dies darauf zurückzuführen, dass wir vor dem Wechsel zum Deeplink mit der ID des Diagramms, das im Stamm liegt, zum Stammfragment zurückgekehrt sind. Und auch in der setPopUpTo () -Methode wird das True-Flag übergeben, das besagt, dass wir nach der Rückkehr zu diesem Bildschirm es auch vom Stapel löschen müssen.

Wir haben uns entschlossen, unseren dotierten Navigator zurückzugeben und das zu beheben, was wir für falsch halten.

Hier ist es das ursprüngliche Problem, das dazu führte, dass der Stapel gelöscht wurde. Wir haben es wie folgt gelöst. Wir prüfen, ob startDestination gleich Null ist, der Startbildschirm, dann verwenden wir ihn und nehmen als Kennung die Kennung des Diagramms. Wenn unsere startDestination-ID nicht Null ist, nehmen wir diese ID aus dem Diagramm, wodurch wir den Stapel nicht löschen und den Diplink über dem vorhandenen Inhalt öffnen können. Optional können Sie auch das Popup true in den Navigationsoptionen entfernen. Theoretisch sollte auch alles funktionieren.
Und schließlich kommt eine stabile Version heraus. Wir waren froh, wir dachten, dass alles in Ordnung war, aber in der stabilen Version änderte sich das Verhalten im Großen und Ganzen nicht. Sie haben es gerade fertiggestellt. Wir haben endlich sichere Argumente zum Laufen gebracht, also haben wir begonnen, Argumente aktiv auf unseren Bildschirmen hinzuzufügen und sie überall im Code zu verwenden. Wir haben auch festgestellt, dass die Navigation mit DialogFragments nicht funktioniert. Da wir über DialogFragments verfügten, wollten wir alle in ein Diagramm in XML übertragen und die Übergänge zwischen ihnen beschreiben. Aber wir haben es nicht geschafft.

Doppelte Öffnung. Wir hatten auch ein Problem, das uns von der ersten Version an heimgesucht hat - das doppelte Öffnen von Activity während eines Kaltstarts der Anwendung.

Es passiert wie folgt. Es gibt eine wunderbare Handle-Deeplink-Methode, die wir aus unserem Code aufrufen können, wenn wir beispielsweise in Aktivität onNewIntent () erhalten, um einen Deeplink abzufangen. Hier wird das ACTIVITY_NEW_TASK-Flag auf Intent gesetzt, wenn die Anwendung über den Deeplink gestartet wird. Folgendes geschieht also: Eine neue Aktivität wird gestartet, und wenn eine aktuelle Aktivität vorhanden ist, wird sie beendet. Wenn wir die Anwendung starten, wird der weiße Bildschirm zuerst gestartet, dann verschwindet er, ein weiterer Bildschirm wird angezeigt und sie sehen sehr schön aus.
Durch die Einführung dieser Bibliothek haben wir die folgenden Vorteile erhalten.

Wir haben eine Dokumentation für unsere Navigation sowie eine grafische Darstellung der Übergänge zwischen Bildschirmen. Wenn eine Person in einem Projekt zu uns kommt, versteht sie dies schnell, indem sie sich die Grafik ansieht und ihre Präsentation im Studio öffnet.
Wir haben SingleActivity. Alle Bildschirme werden auf Fragmenten erstellt, alle Diplinks führen zu Fragmenten, und ich denke, das ist praktisch.
Das Ergebnis war eine einfache Verknüpfung des Deeplinks mit Fragmenten. Fügen Sie einfach das Deeplink-Tag zum Fragment hinzu, und die Bibliothek erledigt alles für uns. Wir haben unsere Navigation auch in verschachtelte Untergraphen unterteilt und eine verschachtelte Navigation erstellt. Das sind verschiedene Dinge. Tatsächlich handelt es sich nur um ein verschachteltes Diagramm, das in das Diagramm aufgenommen wird. Bei verschachtelter Navigation wird ein separater Navigator verwendet, um die Bildschirme zu durchlaufen.
Wir ändern das Diagramm auch dynamisch im Code, wir können Scheitelpunkte hinzufügen, wir können Scheitelpunkte löschen, wir können den Startbildschirm ändern - alles funktioniert.
Wir haben fast vergessen, wie man mit dem FragmentManager arbeitet, da die gesamte Logik der Arbeit mit ihm in der Bibliothek enthalten ist und die Bibliothek die ganze Magie für uns erledigt. Die Bibliothek funktioniert auch mit DrawerLayout. Wenn Sie ein Root-Fragment festgelegt haben, zeichnet die Bibliothek selbst einen Hamburger darauf. Wenn Sie zu den nächsten Bildschirmen wechseln, zeichnet sie einen Pfeil und führt dies animiert aus, wenn Sie vom vorletzten Fragment zurückkehren.
Wir haben auch alle Argumente, die meisten davon, an SafeArgs übertragen, und alles wird beim Erstellen des Projekts erstellt. Darüber hinaus haben wir alle Probleme bewältigt, die uns plagten, und die Bibliothek an unsere Bedürfnisse angepasst.
SafeArgs kann Kotlin-Code generieren, hierfür wird ein separates Plugin verwendet.

Darüber hinaus hat die Bibliothek Nachteile. Das erste ist, dass die Bereinigung des Stapels an die stabile Version geliefert wurde. Ich weiß nicht, warum dies getan wurde, vielleicht ist es für jemanden so praktisch, aber im Fall unserer Anwendung möchten wir Deeplinks über den vorhandenen Inhalt hinaus öffnen.
Fragmente selbst werden vom Navigatorfragment erstellt und durch Reflexion erstellt. Ich denke nicht, dass dies ein Plus bei der Implementierung ist. Die Bedingung für Deeplink wird nicht unterstützt. Ihre Anwendung verfügt möglicherweise über geheime Bildschirme, die nur autorisierten Benutzern zur Verfügung stehen. Und um Deeplinks nach Bedingungen zu öffnen, müssen Sie dafür Krücken schreiben, da Sie keinen Deeplink-Prozessor einstellen können, in dem wir die Kontrolle erhalten und sagen, was zu tun ist. Öffnen Sie entweder den Bildschirm über den Deeplink oder öffnen Sie einen anderen Bildschirm.
Außerdem gehen die Übergangsbefehle verloren, weil das Fragment des Navigators den Status überprüft, ob er gespeichert ist oder nicht. Wenn er gespeichert wird, tun wir einfach nichts.
Wir mussten auch die Bibliothek mit einer Datei ändern, dies ist kein Plus. Und noch ein wesentlicher Nachteil: Wir haben nicht die Möglichkeit, eine Kette von Bildschirmen vor dem Deeplink zu öffnen. Im Falle unserer Bewerbung möchten wir einen Deeplink zum Warenkorb erstellen, vor dem wir alle vorherigen Bildschirme öffnen: Restaurants, ein bestimmtes Restaurant und erst danach den Warenkorb.
Um mit der Navigation arbeiten zu können, benötigen Sie außerdem eine View-Instanz. Um einen Navigator zu erhalten, müssen Sie sich der Ansicht zuwenden - die Navigationsbibliothek selbst kontaktiert die Eltern dieser Ansicht - und versuchen, den Navigator darin zu finden. Wenn sie es findet, geht der Bildschirm zu dem Bildschirm, den wir brauchen.
Aber es stellt sich die Frage: Lohnt es sich, die Bibliothek im Kampf zu nutzen? Ich werde ja sagen, wenn:

Wenn wir ein schnelles Ergebnis erzielen müssen. Die Bibliothek ist sehr schnell implementiert, sie kann in ca. 20 Minuten in das Projekt eingefügt werden, alle Übergänge werden mit der Maus gestochen, all dies ist praktisch. Es ist auch schnell in XML geschrieben, schnell zusammengestellt und funktioniert schnell. Alles super.
Wenn Sie in der Anwendung viele Bildschirme benötigen, die mit Deeplinks arbeiten, und es keine Bedingungen gibt, unter denen sie geöffnet werden sollten.
Keine einzelne Aktivität. Hier meine ich nicht nur einzelne Aktivität. Wir können sowohl für Fragmente mit einer Aktivität als auch für eine Mischung aus Fragmenten und Aktivität navigieren. In der Grafik können Sie auch die Aktivität beschreiben. Hierzu wird im Navigator ein Provider verwendet, in dem sich zwei Navigationsimplementierungen befinden. Die zweite Implementierung ist ein Fragmentnavigator, der intern mit einem Fragmentmanager zusammenarbeitet. Dabei werden Absichten erstellt, um zu den gewünschten Aktivitäten zu gelangen.
Wenn Sie keine komplexe Logik zum Umschalten zwischen Bildschirmen haben. Jede Anwendung ist auf ihre Weise einzigartig. Wenn es eine komplexe Logik zum Öffnen von Bildschirmen gibt, ist diese Bibliothek nichts für Sie.
Sie sind bereit, in die Quelle zu gehen und sie wie wir zu ändern. Das ist eigentlich sehr interessant.

Ich werde nein sagen, wenn die Anwendung eine komplizierte Navigationslogik hat, wenn Sie eine Reihe von Bildschirmen vor dem Deeplink öffnen müssen, wenn Sie schwierige Bedingungen benötigen, um die Bildschirme über den Deeplink zu öffnen. Im Prinzip kann dies mit dem Autorisierungsbeispiel erfolgen, der benötigte Bildschirm wird einfach geöffnet und darüber befindet sich der Autorisierungsbildschirm. Wenn sich der Benutzer nicht anmeldet, werden Sie auf den Stapel zum vorherigen Bildschirm verschoben, vor dem der geheime Bildschirm geöffnet wurde. Eine solche Krückenentscheidung.
Der Verlust von Teams ist für Sie von entscheidender Bedeutung. Derselbe Cicerone kann Befehle im Puffer speichern, und wenn der Navigator verfügbar wird, führt er sie aus.
Wenn Sie den Code nicht fertigstellen möchten, bedeutet dies nicht, dass Sie als Entwickler nichts tun möchten. Angenommen, Sie haben eine Frist. Produktmanager sagen Ihnen, dass Sie Features kürzen müssen und ein Unternehmen Features einführen möchte. Sie benötigen eine schlüsselfertige Lösung, die sofort funktioniert. Bei Navigationskomponenten geht es nicht um diesen Fall.
Eine weitere wichtige Sache - DialogFragments werden nicht unterstützt. Ein Navigator, der mit ihnen funktioniert, könnte hinzugefügt werden, aber aus irgendeinem Grund wurden sie nicht hinzugefügt. Das Problem ist, dass sie sich selbst als gewöhnliche Fragmente öffnen. Das Kontrollkästchen für isShowing ist nicht aktiviert und dementsprechend wird der DialogFragments-Lebenszyklus zum Erstellen eines Dialogfelds nicht ausgeführt. Tatsächlich öffnet DialogFragment als reguläres Fragment den gesamten Bildschirm, ohne ein Fenster zu erstellen.
Das ist alles für mich. Grabe die Quelle, das ist wirklich interessant und aufregend. Hier finden Sie
nützliche Materialien für diejenigen, die an der Arbeit mit der Navigationsbibliothek interessiert sind. Vielen Dank für Ihre Aufmerksamkeit.