In diesem Artikel werden die Entwicklungsphasen der mobilen Meeting Room Helper-Anwendung ausführlich beschrieben: von der Idee bis zur Veröffentlichung. Die Anwendung ist in Kotlin geschrieben und basiert auf einer vereinfachten MVVM-Architektur ohne Datenbindung. Der UI-Teil wird mithilfe von LiveData-Objekten aktualisiert. Die Gründe für die Verweigerung der Datenbindung werden detailliert und erläutert. Die Architektur verwendet eine Reihe interessanter Lösungen, die es ermöglichen, das Programm logisch in kleine Dateien aufzuteilen, was letztendlich die Codeunterstützung vereinfacht.


Projektbeschreibung
Vor 3 Jahren kam in unserem Unternehmen die Idee auf, ein kleines Projekt zur sofortigen Buchung von Tagungsräumen zu entwickeln. Die meisten Personalmanager und Arcadia bevorzugen es, den Outlook-Kalender für solche Zwecke zu verwenden, aber was ist mit dem Rest?
Ich werde 2 Beispiele aus dem Leben des Entwicklers geben
- Jedes Team hat regelmäßig den spontanen Wunsch, eine schnelle Rallye für 5-10 Minuten abzuhalten. Dieser Wunsch kann Entwickler in jeder Ecke des Büros überholen, und um Kollegen in ihrer Umgebung nicht abzulenken, suchen sie (Entwickler und nicht nur) nach einem freien Gespräch. Bei der Migration von Raum zu Raum (in unserem Büro sind die Besprechungsräume in einer Reihe angeordnet) überprüfen die Kollegen sorgfältig, welcher der Räume derzeit frei ist. Infolgedessen lenken sie Kollegen im Inneren ab. Solche Leute waren und werden es immer sein, auch wenn die Dreharbeiten in der Unternehmenscharta wegen Unterbrechung der Rallye beendet sind. Wer verstanden hat, wird verstehen.
- Und hier ist ein anderer Fall. Sie haben gerade den Speisesaal verlassen und gehen zu sich selbst, aber hier fängt Sie Ihr Kollege (oder Manager) aus einer anderen Abteilung ab. Er möchte Ihnen etwas Dringendes sagen, und für diese Zwecke benötigen Sie einen Besprechungsraum. Gemäß den Vorschriften müssen Sie zuerst ein Zimmer (von Ihrem Telefon oder Computer) buchen und erst dann belegen. Es ist gut, wenn Sie ein Mobiltelefon mit mobilem Outlook haben. Und wenn nicht? Zurück zum Computer, dann wieder zum Besprechungsraum zurückkehren? Um jeden Mitarbeiter zu zwingen, Outlook Express auf das Telefon zu setzen und sicherzustellen, dass jeder die Telefone mit sich führt? Dies sind nicht unsere Methoden.
Deshalb wurde vor 2,5 Jahren jeder Tagungsraum mit einem eigenen Tablet ausgestattet:

Für dieses Projekt hat mein Kollege die erste Version der Anwendung entwickelt: Meeting Room Little Helper (
hier können Sie darüber lesen ). MRLH, das eine Reservierung buchen, eine Reservierung stornieren und erneuern durfte, zeigte den Status der verbleibenden Gespräche an. Das Erkennen der Identität eines Mitarbeiters (mithilfe des Microsoft Face API-Clouddiensts und unserer internen Analysegeräte) ist zu einem innovativen „Trick“ geworden. Die Bewerbung erwies sich als solide und diente dem Unternehmen 2,5 Jahre lang treu.
Aber die Zeit verging ... Neue Ideen erschienen. Ich wollte etwas Frisches, und so beschlossen wir, die Anwendung neu zu schreiben.
Technische Aufgabe
Wie so oft - aber leider nicht immer - begann die Entwicklung mit der Erstellung technischer Spezifikationen. Zuerst haben wir die Leute angerufen, die am häufigsten Tablets für Reservierungen verwenden. Es war einfach so, dass sie vor allem von HRs und Managern abhängig waren, die zuvor ausschließlich Outlook verwendet hatten. Von ihnen erhielten wir das folgende Feedback (aus den Anforderungen geht sofort hervor, wonach die Personalabteilung und die Manager gefragt haben):
- Sie müssen die Möglichkeit hinzufügen, einen beliebigen Besprechungsraum von einem beliebigen Tablet aus zu buchen (zuvor konnten Sie auf jedem Tablet nur Ihren Raum buchen).
- Es wäre cool, sich den Zeitplan der Kundgebungen für ein ganztägiges Treffen anzusehen (idealerweise für jeden Tag).
- Der gesamte Entwicklungszyklus muss in kurzer Zeit (für 6-7 Wochen) durchgeführt werden.
Mit den Kundenwünschen ist alles klar, aber was ist mit den technischen Anforderungen und der Zukunft? Fügen Sie der Entwicklergilde einige Anforderungen für das Projekt hinzu:
- Das System sollte sowohl mit vorhandenen als auch mit neuen Tablets funktionieren.
- Skalierbarkeit des Systems - ab 50 Konversationen (dies sollte für die meisten Kunden mit einem Spielraum ausreichen, wenn das System mit der Replikation beginnt);
- Beibehaltung der vorherigen Funktionalität (die erste Version der Anwendung verwendete die Java-API für die Kommunikation mit Outlook-Diensten, und wir planten, sie durch eine spezielle Microsoft Graph-API zu ersetzen, sodass es wichtig war, die Funktionalität nicht zu verlieren);
- Minimierung des Energieverbrauchs (Tablets werden von einer externen Batterie gespeist, da das Business Center das Bohren seiner Wände zum Verlegen unserer Drähte nicht zuließ);
- neues UX / UI-Design, das alle Innovationen ergonomisch widerspiegelt.
Insgesamt 8 Punkte. Die Anforderungen sind ziemlich fair. Zusätzlich legen wir die allgemeinen Entwicklungsregeln fest:
- Verwenden Sie nur fortschrittliche Technologien (dies ermöglicht es dem Team, sich als Spezialisten zu entwickeln und nicht an einem Ort zu stagnieren, während die Projektunterstützung in absehbarer Zukunft vereinfacht wird).
- Befolgen Sie die Best Practices, aber halten Sie sie nicht blind für selbstverständlich Die Hauptregel eines jeden Profis (und eines Entwicklers, der danach strebt) ist, alles kritisch zu bewerten.
- Schreiben von sauberem und ordentlichem Code (dies ist möglicherweise am schwierigsten, wenn Sie versuchen, Innovation und enge Entwicklungszeit zu kombinieren).
Ein Anfang wurde gemacht. Es ist wie immer begeistert! Mal sehen, was als nächstes passiert.
Design
Vom UX-Designer entwickeltes Anwendungsdesign:


Dies ist der Hauptbildschirm. Es wird die meiste Zeit angezeigt. Alle notwendigen Informationen finden Sie hier ergonomisch:
- den Namen des Raumes und seine Nummer;
- aktueller Status;
- Zeit bis zum nächsten Treffen (oder bis zu seinem Ende);
- den Status der verbleibenden Räume am unteren Bildschirmrand.
Bitte beachten Sie: Auf dem Zifferblatt werden nur 12 Stunden angezeigt Das System ist auf die Anforderungen des Unternehmens konfiguriert (Arcadia-Tablets funktionieren von 8 bis 20 Uhr, werden automatisch ein- und ausgeschaltet).


Um ein Zimmer zu buchen, rufen Sie einfach das Buchungsfenster an und geben Sie die Dauer der Rallye an. Die Schritte zum Buchen der verbleibenden Zimmer sind ähnlich. Sie beginnen erst mit einem Klick auf das Zimmersymbol.


Wenn Sie eine Besprechung für eine bestimmte Zeit planen möchten, wechseln Sie zur nächsten Registerkarte in der Liste der Besprechungen, die heute im Besprechungsraum stattfinden, und klicken Sie auf Freizeit. Weiter ist alles wie im ersten Fall.
Der vollständige Übergangsbaum sollte ungefähr so aussehen:


Versuchen wir es kompetent umzusetzen.
Technologie-Stack
Entwicklungstechniken entwickeln sich ziemlich schnell und ändern sich. Für weitere 2 Jahre war Java die offizielle Android-Entwicklungssprache. Jeder schrieb in Java und verwendete Datenbindung. Nun, so scheint es mir, bewegen wir uns in Richtung reaktiver Programmierung und Kotlin. Java ist eine großartige Sprache, weist jedoch einige Mängel auf, verglichen mit dem, was Kotlin und AndroidX zu bieten haben. Kotlin und AndroidX können die Verwendung der Datenbindung auf ein Minimum reduzieren, wenn nicht sogar vollständig ausschließen. Im Folgenden werde ich versuchen, meinen Standpunkt zu erläutern.
Kotlin
Ich denke, viele Android-Entwickler sind bereits zu Kotlin gewechselt, und deshalb werden sie mir zustimmen, dass das Schreiben eines neuen Android-Projekts im Jahr 2019 in einer anderen Sprache als Kotlin dem Kampf gegen das Meer gleicht. Natürlich können Sie streiten, aber was ist mit Flutter und Dart? Was ist mit C ++, C # und sogar Cordova? Worauf ich antworten werde: Die Wahl liegt immer bei Ihnen.
480 v Der persische König Xerxes befahl seinen Soldaten, das Meer zu überqueren, um einen Teil seiner Armee während eines Sturms zu zerstören. Fünf Jahrhunderte später erklärte der römische Kaiser Caligula Poseidon den Krieg. Geschmackssache. Für 9 von 10 ist Kotlin gut, aber für 10 kann es schlecht sein. Es hängt alles von Ihnen ab, von Ihren Wünschen und Bestrebungen.
Kotlin ist meine Wahl. Die Sprache ist einfach und schön. Das Schreiben darauf ist einfach und angenehm, und vor allem muss nicht zu viel geschrieben werden: Datenklasse, Objekt, optionaler Setter und Getter, einfache Lambda-Ausdrücke und Erweiterungsfunktionen. Dies ist nur ein winziger Teil dessen, was diese Sprache zu bieten hat. Wenn Sie noch nicht zu Kotlin gewechselt sind, können Sie loslegen! Im Abschnitt mit Übung werde ich einige der Vorteile der Sprache demonstrieren (es ist kein Werbeangebot).
Model-View-ViewModel
MVVM ist derzeit die empfohlene Anwendungsarchitektur von Google. Während der Entwicklung werden wir uns an dieses spezielle Muster halten, es jedoch nicht vollständig einhalten, da MVVM die Verwendung der Datenbindung empfiehlt, wir es jedoch ablehnen.
Vorteile von MVVM- Differenzierung von Geschäftslogik und Benutzeroberfläche. Bei der korrekten Implementierung von MVVM sollte in ViewModel kein einziger Android-Import vorhanden sein, außer für LiveData-Objekte aus AndroidX- oder Jetpack-Paketen. Bei ordnungsgemäßer Verwendung bleiben alle UI-Arbeiten automatisch in Fragmenten und Aktivitäten. Ist das nicht toll?
- Der Einkapselungsgrad wird gepumpt. Es wird einfacher sein, als Team zu arbeiten: Jetzt können Sie alle auf einem Bildschirm zusammenarbeiten und sich nicht gegenseitig stören. Während ein Entwickler mit dem Bildschirm arbeitet, kann ein anderer ein ViewModel erstellen und ein dritter kann Abfragen in das Repository schreiben.
- MVVM wirkt sich positiv auf das Schreiben von Unit-Tests aus. Dieser Punkt folgt aus dem vorherigen. Wenn alle Klassen und Methoden für die Arbeit mit der Benutzeroberfläche gekapselt sind, können sie problemlos getestet werden.
- Eine natürliche Lösung mit Bildschirmrotation. Egal wie seltsam es auch klingen mag, diese Funktion wird beim Übergang zu MVVM automatisch erfasst (da die Daten im ViewModel gespeichert sind). Wenn Sie die recht beliebten Anwendungen (VK, Telegramm, Sberbank-Online und Aviasales) überprüfen, stellt sich heraus, dass genau die Hälfte von ihnen den Bildschirm nicht drehen kann. Was mich als Benutzer dieser Anwendungen überrascht und missversteht.
Warum ist MVVM gefährlich?- Speicherverlust. Dieser gefährliche Fehler tritt auf, wenn Sie gegen die Gesetze zur Verwendung von LiveData und Observer verstoßen. Wir werden diesen Fehler im Übungsabschnitt ausführlich untersuchen.
- Weitläufiges ViewModel. Wenn Sie versuchen, die gesamte Geschäftslogik in das ViewModel zu integrieren, erhalten Sie einen unlesbaren Code. Der Ausweg aus dieser Situation kann darin bestehen, das ViewModel in eine Hierarchie aufzuteilen oder Präsentatoren zu verwenden. Genau das habe ich getan.
Regeln für die Arbeit mit MVVMBeginnen wir mit den meisten Fehlern und gehen wir zu den weniger Fehlern:
- Der Anfragetext sollte sich nicht in ViewModel befinden (nur im Repository).
- LiveData-Objekte werden im ViewModel definiert, sie werfen sich nicht in das Repository, weil Anforderungen im Repository werden mit Rx-Java (oder Coroutinen) verarbeitet.
- Alle Verarbeitungsfunktionen sollten in Klassen und Dateien von Drittanbietern ("Präsentatoren") verschoben werden, um das ViewModel nicht zu überladen und nicht vom Wesen abzulenken.
Livedata
LiveData ist eine beobachtbare Datenhalterklasse. Im Gegensatz zu einem regulären Observable ist LiveData lebenszyklusabhängig, dh es berücksichtigt den Lebenszyklus anderer App-Komponenten wie Aktivitäten, Fragmente oder Dienste. Diese Kenntnis stellt sicher, dass LiveData nur Beobachter von App-Komponenten aktualisiert, die sich in einem aktiven Lebenszyklus befinden.
Quelle: developer.android.com/topic/libraries/architecture/livedataAus der Definition kann eine einfache Schlussfolgerung gezogen werden: LiveData ist ein zuverlässiges reaktives Programmierwerkzeug. Wir werden es verwenden, um den UI-Teil ohne Datenbindung zu aktualisieren. Warum so?
Die Struktur der XML-Dateien ermöglicht keine präzise Verteilung der aus <data> ... </ data> erhaltenen Daten. Wenn bei kleinen Dateien alles klar ist, was ist dann mit großen Dateien? Was tun mit komplexen Bildschirmen, mehreren Einschließen und Übergeben mehrerer Felder? Modelle überall verwenden? Steife Feldbindungen bekommen? Und wenn das Feld formatiert werden soll, Methoden aus Java-Paketen aufrufen? Dies macht den Code hoffnungslos und vollständig Spaghetti. Überhaupt nicht das, was MVVM versprochen hat.
Durch das Ablehnen der Datenbindung werden Änderungen am UI-Teil transparent. Alle Aktualisierungen erfolgen direkt im Beobachter. Weil Da der Kolin-Code präzise und klar ist, werden wir keine Probleme mit aufgeblähten Beobachtern bekommen. Das Schreiben und Verwalten von Code wird einfacher. XML-Dateien werden nur für das Design verwendet - keine Eigenschaft im Inneren.
Die Datenbindung ist ein leistungsstarkes Werkzeug. Es eignet sich hervorragend zum Lösen einiger Probleme und harmoniert gut mit Java, aber mit Kotlin ... Bei Kotlin ist die Datenbindung in den meisten Fällen nur rudimentär. Die Datenbindung verkompliziert nur den Code und bietet keine Wettbewerbsvorteile.
In Java hatten Sie die Wahl: Verwenden Sie entweder die Datenbindung oder schreiben Sie viel hässlichen Code. In Kotlin können Sie direkt auf Ansichtselemente zugreifen, indem Sie findViewById () sowie dessen Eigenschaft umgehen. Zum Beispiel:
Es stellt sich eine logische Frage: Warum sollten Sie sich mit Gartenmodellen in XML-Dateien beschäftigen, Java-Methoden in XML-Dateien aufrufen und die Logik des XML-Teils überladen, wenn all dies vermieden werden kann?
Coroutinen anstelle von Thread () und Rx-Java
Coroutinen sind unglaublich leicht und einfach zu bedienen. Sie sind ideal für die meisten einfachen asynchronen Aufgaben: Verarbeiten von Abfrageergebnissen, Aktualisieren der Benutzeroberfläche usw.
Coroutinen können Thread () und Rx-Java effektiv ersetzen, wenn keine hohe Leistung erforderlich ist, weil Sie bezahlen für Leichtigkeit mit Geschwindigkeit. Rx-Java ist zweifellos funktionaler, für einfache Aufgaben sind jedoch nicht alle Ressourcen erforderlich.
Microsoft und der Rest
Für die Arbeit mit Outlook-Diensten wird die Microsoft Graph-API verwendet. Mit den entsprechenden Berechtigungen erhalten Sie alle erforderlichen Informationen zu Mitarbeitern, Räumen und Event-Ahs (Besprechungen). Zur Gesichtserkennung wird der Microsoft Face API-Clouddienst verwendet.
Mit Blick auf die Zukunft werde ich sagen, dass zur Lösung des Skalierbarkeitsproblems Firebase-Cloud-Speicher verwendet wurde. Dies wird unten diskutiert.
Architektur
Skalierbarkeitsprobleme
Es ist ziemlich schwierig, das System vollständig oder teilweise skalierbar zu machen. Dies ist besonders schwierig, wenn die erste Version der Anwendung nicht skalierbar war und die zweite Version skalierbar sein sollte. Die Anwendung v1 hat gleichzeitig Anfragen an alle Räume gesendet. Jedes der Tablets hat regelmäßig Anforderungen an den Server gesendet, um alle Daten zu aktualisieren. Gleichzeitig haben sich die Geräte nicht miteinander synchronisiert, weil Das Projekt hat einfach keinen eigenen Server.
Wenn wir denselben Weg gehen und N Anforderungen von jedem der N Tablets senden, werden wir natürlich irgendwann entweder die Microsoft Graph-API umkippen oder unser System einfrieren lassen.
Es wäre logisch, eine Client-Server-Lösung zu verwenden, bei der der Server das Diagramm abfragt, Daten sammelt und auf Anfrage Informationen für die Tablets bereitstellt. Hier trifft uns jedoch die Realität. Das Projektteam besteht aus 2 Personen (Android-Entwickler und -Designer). Sie müssen die Frist von 7 Wochen einhalten und das Backend wird nicht zur Verfügung gestellt, weil Skalierung ist eine Anforderung des Entwicklers. Das heißt aber nicht, dass die Idee aufgegeben werden muss?
Die wahrscheinlich einzig richtige Lösung in dieser Situation ist die Verwendung von Cloud-Speicher. Firebase ersetzt den Server und fungiert als Puffer. Dann stellt sich Folgendes heraus:
Jedes Tablet fragt nur seine Adresse über die Microsoft Graph-API ab und synchronisiert bei Bedarf Daten im Cloud-Speicher, von wo aus sie von anderen Geräten gelesen werden können.Der Vorteil dieser Implementierung ist eine schnelle Reaktion, weil Firebase arbeitet im Echtzeitmodus. Wir werden die Anzahl der Anfragen, die N-mal an den Server gesendet werden, reduzieren, was bedeutet, dass das Gerät etwas länger im Akkubetrieb arbeitet. Aus finanzieller Sicht ist das Projekt nicht im Preis gestiegen, weil Für dieses Projekt reicht die kostenlose Version von Firebase mit mehreren Reserven aus: 1 GB Speicher, 10.000 Autorisierungen pro Monat und 100 Verbindungen gleichzeitig. Zu den Nachteilen könnte die Abhängigkeit von einem Framework eines Drittanbieters gehören, aber Firebase schafft Vertrauen in uns, weil Es ist ein stabiles Produkt, das von Google gepflegt und entwickelt wird.
Die Grundidee des neuen Systems lautete wie folgt: N Tablets und eine Cloud-Plattform für die Echtzeit-Datensynchronisation. Beginnen wir mit dem Entwerfen der Anwendung.
LiveData im Repository
Es scheint, dass ich kürzlich die Regeln für gute Form festgelegt habe und sofort gegen eine von ihnen verstoße. Im Gegensatz zur empfohlenen Verwendung von LiveData im ViewModel werden in diesem Projekt LiveData-Objekte im Repository initialisiert und alle Repositorys als Singleton deklariert. Warum so?
Eine ähnliche Lösung ist mit dem Anwendungsmodus verbunden. Tabletten sind von 8 bis 20 Uhr geöffnet. Während dieser ganzen Zeit wurde nur der Meeting Room Helper auf ihnen gestartet. Infolgedessen können und sollten viele Objekte langlebig sein (aus diesem Grund sind alle Repositorys als Singleton konzipiert).
Im Laufe der Arbeit wird der UI-Inhalt regelmäßig gewechselt, was wiederum die Erstellung und Neuerstellung von ViewModel-Objekten beinhaltet. Wenn Sie LiveData im ViewModel verwenden, wird für jedes erstellte Fragment ein eigenes ViewModel mit einer Reihe angegebener LiveData-Objekte erstellt. Wenn zwei ähnliche Fragmente gleichzeitig mit unterschiedlichem ViewModel und einem gemeinsamen Base-ViewModel auf dem Bildschirm angezeigt werden, werden während der Initialisierung LiveData-Objekte aus dem Base-ViewModel dupliziert. In Zukunft werden diese Duplikate Speicherplatz beanspruchen, bis sie vom "Garbage Collector" zerstört werden. Weil Wenn wir bereits ein Repository in Form eines Singletons haben und die Kosten für die Neuerstellung von Bildschirmen minimieren möchten, ist es ratsam, LiveData-Objekte in ein Singleton-Repository zu übertragen, um ViewModel-Objekte zu vereinfachen und die Anwendung zu beschleunigen.
Dies bedeutet natürlich nicht, dass Sie alle LiveData vom ViewModel in das Repository übertragen müssen, aber Sie sollten dieses Problem sorgfältiger angehen und Ihre Wahl bewusst treffen. Der Nachteil dieses Ansatzes ist die Zunahme der Anzahl langlebiger Objekte, weil Alle Repositorys sind als Singleton definiert und in jedem von ihnen werden LiveData-Objekte gespeichert. In einem bestimmten Fall ist Meeting Room Helper jedoch kein Minus, weil Die Anwendung wird den ganzen Tag ohne Unterbrechung ausgeführt, ohne den Kontext zu anderen Anwendungen zu wechseln.
Resultierende Architektur

- Alle Anforderungen werden in Repositorys ausgeführt. Alle Repositorys (im Meeting Room Helper gibt es 11 davon) sind als Singleton konzipiert. Sie sind nach Art der zurückgegebenen Objekte unterteilt und hinter den Fassaden versteckt.
- Die Geschäftslogik befindet sich im ViewModel. Dank der Verwendung von "Presenters" betrug die Gesamtgröße aller ViewModel (es gibt 6 im Projekt) weniger als 120 Zeilen.
- Aktivität und Fragment sind nur beim Ändern des UI-Teils mithilfe von Observer und LiveData beteiligt, die vom ViewModel zurückgegeben werden.
- Funktionen zum Verarbeiten und Generieren von Daten werden im "Presenter" gespeichert. Aktiv genutzte Berechtigungsfunktionen von Kotlin für die Datenverarbeitung.
Die Hintergrundlogik wurde in Intent-Service verschoben:
- Event-Update-Service. Dienst, der für die Synchronisierung der Daten des aktuellen Raums in der Firebase- und Graph-API verantwortlich ist.
- User-Recognize-Service. Läuft nur auf dem Master-Tablet. Verantwortlich für das Hinzufügen neuer Mitarbeiter zum System. Überprüft eine Liste bereits geschulter Personen mit einer Liste aus Active Directory. Wenn neue Personen angezeigt werden, fügt der Dienst sie der Face-API hinzu und schult das neuronale Netzwerk neu. Nach Abschluss des Vorgangs wird es ausgeschaltet. Es beginnt mit dem Start der Anwendung.
- Der Online-Benachrichtigungsdienst benachrichtigt andere Tablets, dass dieses Tablet funktioniert, d. H. Die externe Batterie ist nicht erschöpft. Es funktioniert über Firebase.
Das Ergebnis war eine im Hinblick auf die Verteilung der Verantwortlichkeiten recht flexible und korrekte Architektur, die alle Anforderungen der modernen Entwicklung erfüllt. Wenn wir in Zukunft die Microsoft Graph-API, Firebase oder ein anderes Modul aufgeben, können diese problemlos durch neue ersetzt werden, ohne den Rest der Anwendung zu beeinträchtigen. Das Vorhandensein eines umfangreichen Systems von „Präsentatoren“ ermöglichte es, alle Datenverarbeitungsfunktionen über den Kern hinaus zu übernehmen. Infolgedessen ist die Architektur kristallklar geworden, was ein großes Plus ist. Das Problem eines überwucherten ViewModel ist vollständig verschwunden.
Im Folgenden werde ich ein Beispiel für das häufig verwendete Bundle in einer entwickelten Anwendung geben.
Übe. Updates ansehen
Abhängig vom Status des Besprechungsraums zeigt das Zifferblatt eine der folgenden Bedingungen an:


Darüber hinaus befinden sich temporäre Rallye-Bögen entlang des Umrisses des Zifferblatts, und die Mitte zählt bis zum Ende des Meetings oder bis zum Beginn der nächsten Rallye herunter. All dies erledigt die von uns entwickelte Canvas-Bibliothek. Wenn sich das Raster der Besprechungen geändert hat, müssen wir die Daten in der Bibliothek aktualisieren.
Da LiveData in Repositories angekündigt wird, ist es am logischsten, mit ihnen zu beginnen.
Repositories
FirebaseRoomRepository - eine Klasse, die für das Senden und Verarbeiten von Anforderungen in Firebase im Zusammenhang mit dem Raummodell verantwortlich ist.
Um dies zu demonstrieren, wurde der Initialisierungscode der Listener-Firebase leicht vereinfacht (die Funktion zum erneuten Verbinden wurde entfernt). Werfen wir einen Blick auf die Punkte, die hier geschehen:
- Das Repository ist als Singleton konzipiert (in Kotlin reicht es aus, das Schlüsselwort class durch object zu ersetzen).
- Initialisierung von LiveData-Objekten;
- ValueEventListener wird als Variable deklariert, um zu vermeiden, dass bei erneuter Verbindung eine anonyme Klasse neu erstellt wird (denken Sie daran, ich habe die Initialisierung vereinfacht, indem ich bei erneuter Verbindung die erneute Verbindung entfernt habe).
- Initialisierung von ValueEventListener (wenn sich die Daten in Firebase ändern, führt der Listener die Daten in LiveData-Objekten sofort aus und aktualisiert sie);
- Aktualisierungen von LiveData-Objekten.
Die Funktionen selbst werden in eine separate FirebaseRoomRepositoryPresenter-Datei verschoben und als Erweiterungsfunktionen dekoriert.
fun MutableLiveData<List<Room>>.updateOtherRooms(rooms: MutableList<Room>) { this.postValue(rooms.filter { !it.isOwnRoom() }) }
Beispiel für eine Erweiterungsfunktion aus FirebaseRoomRepositoryPresenterFür ein allgemeines Verständnis des Bildes werde ich auch eine Auflistung des Raumobjekts geben.
- Datenklasse. Dieser Modifikator generiert automatisch die Methoden toString (), HashCode () und same () und überschreibt sie. Sie müssen sie nicht mehr selbst neu definieren.
- Die Ereignisliste aus dem Raumobjekt. Diese Liste ist erforderlich, um die Daten in der Wählbibliothek zu aktualisieren.
Alle Repositories-Klassen sind hinter der Fassadenklasse versteckt.
object Repository {
- Oben sehen Sie eine Liste aller verwendeten Repository-Klassen und Fassaden der zweiten Ebene. Dies vereinfacht das allgemeine Verständnis des Codes und zeigt eine Liste aller verbundenen Repository-Klassen.
- Eine Liste von Methoden, die Verweise auf LiveData-Objekte aus dem FirebaseRoomRepository zurückgeben. Kotlins Setter und Getter sind optional, sodass Sie sie nicht unnötig schreiben müssen.
Mit einer solchen Organisation können Sie bequem 20 bis 30 Anforderungen in ein Root-Repository einfügen. Wenn Ihre Anwendung mehr Anforderungen hat, müssen Sie die Grundfassade in zwei oder mehr teilen.
ViewModel
BaseViewModel ist das Basis-ViewModel, von dem alle ViewModels geerbt werden. Es enthält ein einzelnes currentRoom-Objekt, das universell verwendet wird.
- Der offene Marker bedeutet, dass Sie von der Klasse erben können. Standardmäßig sind in Kotlin alle Klassen und Methoden endgültig, d. H. Klassen können nicht vererbt und Methoden nicht neu definiert werden. Dies dient zum Schutz vor versehentlichen inkompatiblen Versionsänderungen. Ich werde ein Beispiel geben.
Sie entwickeln eine neue Version der Bibliothek. Aus dem einen oder anderen Grund entscheiden Sie sich irgendwann, die Klasse umzubenennen oder die Signatur einer Methode zu ändern. Durch Ändern haben Sie versehentlich eine Versionsinkompatibilität erstellt. Ups ... Wenn Sie wahrscheinlich gewusst hätten, dass die Methode von jemandem überschrieben werden könnte und die Klasse geerbt wurde, wären Sie wahrscheinlich genauer gewesen und hätten sich kaum in den Fuß geschossen. Zu diesem Zweck wird in Kotlin standardmäßig alles als endgültig deklariert, und zum Abbrechen gibt es einen "offenen" Modifikator.
- Die Methode getCurrentRoom () gibt einen Link zum LiveData-Objekt des aktuellen Raums aus dem Repository zurück, der wiederum aus dem FirebaseRoomRepository stammt. Wenn diese Methode aufgerufen wird, gibt das Room-Objekt alle Informationen zum Raum zurück, einschließlich einer Liste von Ereignissen.
Um Daten von einem Format in ein anderes zu konvertieren, verwenden wir die Transformation. Erstellen Sie dazu ein
MainFragmentViewModel und erben Sie es von
BaseViewModel .
MainFragmentViewModel ist eine von BaseViewModel
abgeleitete Klasse. Dieses ViewModel wird nur in MainFragment verwendet.
- Beachten Sie das Fehlen des Modifikators open. Dies bedeutet, dass niemand von der Klasse erbt.
- currentRoomEvents - Ein Objekt, das mithilfe der Transformation erhalten wurde. Sobald sich das Objekt des aktuellen Raums ändert, wird die Transformation durchgeführt und das Objekt currentRoomEvents aktualisiert.
- MediatorLiveData. Das Ergebnis ist identisch mit der Transformation (als Referenz gezeigt).
Die erste Option wird verwendet, um Daten von einem Typ in einen anderen zu konvertieren, was wir brauchten, und die zweite Option wird benötigt, um eine Geschäftslogik auszuführen. Eine Datenkonvertierung findet jedoch nicht statt. Denken Sie daran, dass der Android-Import in ViewModel nicht gültig ist. Daher starte ich von hier aus zusätzliche Anfragen oder starte die Dienste nach Bedarf neu.
Wichtiger Hinweis! Damit die Transformation oder der Mediator funktioniert, muss jemand von einem Fragment oder einer Aktivität abonniert werden. Andernfalls wird der Code nicht ausgeführt, weil Niemand wird ein Ergebnis erwarten (dies sind Beobachterobjekte).
Mainfragment
Der letzte Schritt bei der Konvertierung von Daten in Ergebnisse. MainFragment enthält eine Wählbibliothek und einen View-Pager am unteren Bildschirmrand.
class MainFragment : BaseFragment() {
- Initialisierung von MainFragmentViewModel. Der Modifikator lateinit gibt an, dass wir versprechen, dieses Objekt später zu initialisieren, bevor wir es verwenden. Kotlin versucht, den Programmierer vor falschem Schreiben von Code zu schützen, daher müssen wir entweder sofort sagen, dass das Objekt null sein kann, oder lateinit setzen. In diesem Fall muss das ViewModel vom Objekt initialisiert werden.
- Beobachter-Listener, um das Zifferblatt zu aktualisieren.
- ViewModel initialisieren. Bitte beachten Sie, dass dies unmittelbar nach dem Anhängen des Fragments an die Aktivität geschieht.
- Nachdem die Aktivität erstellt wurde, abonnieren wir Änderungen am currentRoomEvents-Objekt. Bitte beachten Sie, dass ich nicht den Fragmentlebenszyklus (this) abonniere, sondern das viewLifecycleOwner-Objekt. Tatsache ist, dass in der Support-Bibliothek 28.0.0 und AndroidX 1.0.0 ein Fehler festgestellt wurde, als der Beobachter "abgemeldet" wurde. Um dieses Problem zu lösen, wurde ein Patch in Form von viewLifecycleOwner veröffentlicht, und Google empfiehlt, ihn zu abonnieren. Dies behebt das Problem des Zombie-Beobachters, wenn das Fragment gestorben ist und der Beobachter weiter arbeitet. Wenn Sie dies weiterhin verwenden, müssen Sie es durch viewLifecycleOwner ersetzen.
Daher möchte ich die Einfachheit und Schönheit von MVVM und LiveData ohne Datenbindung demonstrieren. Bitte beachten Sie, dass ich in diesem Projekt gegen die allgemein akzeptierte Regel verstoße, indem ich LiveData aufgrund der Besonderheiten des Projekts in das Repository stelle. Wenn wir sie jedoch in das ViewModel verschieben, bleibt das Gesamtbild unverändert.
Als Kirsche auf einem Kuchen habe ich für Sie ein kurzes Video mit einer Demonstration vorbereitet (Namen werden gemäß den Sicherheitsanforderungen verschmiert, ich entschuldige mich):

Zusammenfassung
Infolge der Anwendung im ersten Monat wurden einige Fehler bei der Anzeige von Cross-Rallyes aufgedeckt (mit Outlook können Sie mehrere Ereignisse gleichzeitig erstellen, während unser System dies nicht tut). Jetzt arbeitet das System seit 3 Monaten. Fehler oder Ausfälle werden nicht beobachtet.
PS Danke
jericho_code für den Kommentar. In Kotlin können und sollten Sie List <> im Modell mit emptyList () initialisieren, dann wird kein zusätzliches Objekt erstellt.
var events: List<Event.Short> = emptyList()