Wir haben also unser Thema definiert, wie können wir dieses neue Thema nun zu komponierenden Elementen wie einer Schaltfläche hinzufügen? Beim ersten Versuch habe ich folgendes gemacht:
Und das Ergebnis war:
Nun, das sieht nicht so aus, wie wir es in unserem Thema definiert haben. Nicht wahr? Also habe ich mir angesehen, wie die Schaltfläche in MaterialTheme definiert ist
Wie ich schnell lernte (was, ehrlich gesagt, vollkommen erwartet wurde, verstand ich nicht nur anfangs die Auswirkungen der Erstellung unseres eigenen Designs), verwendet Button MaterialTheme-Attribute, die wir nicht definiert haben. Das bedeutet dann, dass wir die Button-Attribute komplett durch unsere eigenen ersetzen müssen. Und das taten wir.
Was wiederum diese Schaltfläche erstellt
Ok, es sieht besser aus, aber die Typografie sieht komisch aus, oder? Also habe ich mir den Material-Button-Code angesehen und wir können Folgendes sehen:
Nun, es scheint unseren TextStyle mit MaterialTheme-Typografie zu überwältigen. Ok, wir können das beheben, indem wir unseren eigenen TextStyle hinzufügen. Also ersetzen wir unseren Text durch den folgenden Code, der das Endergebnis liefert
Okay! Jetzt sieht es aus wie einer unserer Buttons!
Wie wir bereits gelernt haben, müssen wir Compilation-Theme-Materialien mit unseren Theme-Attributen in unsere eigenen Compiler packen. Lassen Sie uns versuchen, eine Oberfläche zu schaffen, die zu dunklen und hellen Themen passt. Auf den ersten Blick könnten wir einfach das mitgelieferte Surface verwenden. Aber jetzt erwarten wir schon, dass die Dinge nicht so funktionieren, wie wir wollen, oder?
Das helle Thema sieht gut aus (aber ist es das wirklich?), aber das dunkle ist nichts im Vergleich zu dem, was wir erwartet haben. Es sieht überhaupt nicht düster aus! Aber jetzt wissen wir, was das Problem ist. Wenn wir uns noch einmal die Definition von Compose Surface ansehen, sehen wir Folgendes:
Ja. Verwendet MaterialTheme-Farben. Also lassen Sie es uns um unsere Oberfläche wickeln
Ja! Das wollen wir. Hier sind einige Dinge zu beachten.
Wir haben die Hintergrundfarbe als Oberflächenfarbe definiert. Aber hier wird es interessant. Da Sie Ihr eigenes Thema definieren, können Sie frei entscheiden, welche Farbe Sie für Ihre Oberflächen wünschen, Sie können jedes Farbattribut verwenden, das Sie in Ihrem Thema definiert haben: Primär, Sekundär, Hintergrund, Oberfläche oder sogar Banane.
Aufmerksamere Leser haben vielleicht bemerkt, dass wir unsere eigenen definieren InhaltFarbe wie defaultContentColorFor (Farbe) Was ist das?
MaterialTheme gibt uns die Methode, die ist contentColorFor (Farbe) aber wir können es nicht verwenden, weil es die MaterialTheme-Spezifikation verwendet, die zur MaterialTheme-Farbgebung zurückkehrt. Daher verwenden wir unsere Methode, um die Farben unseres Themas abzurufen
Durch die Verwendung unserer Komponentenelemente, die vordefinierte Komponenten umschließen, und durch das Ändern einiger Attribute, wie z. B. der Farbe, können wir unser Design vollständig anpassen und die korrekte Anwendung der Dark- und Light-Eigenschaften ermöglichen.
Ich hoffe, dass Sie mit diesem Wissen diese Logik auf jede gewünschte Komponente anwenden und Ihr eigenes brandneues benutzerdefiniertes Design erstellen können.
Kotlin und Swift haben vielleicht viel funktionale Magie in das Spiel eingeführt, aber da sie die natürlichen Erben von Java und Obj-Ca sind, haben sie starke OOP-Grundlagen. Vererbung ist immer noch ein grundlegendes Konzept, und Schnittstellen / Protokolle spielen eine wichtige Rolle im Toolkit eines mobilen Entwicklers.
Aber wie bei allem gibt es echte Gründe, sie zu verwenden, und es gibt falsche. Beim Code-Design bemühen wir uns, bewährte Verfahren zu befolgen, auch wenn wir uns nicht zu 100 % sicher sind, warum sie gut sind. Und obwohl es überhaupt keine schlechte Sache ist, kann es zu subtilem Missbrauch führen. Wenn wir uns zum Beispiel an die protokollorientierte Programmierung gewöhnen, verspüren wir möglicherweise das Bedürfnis, sie auf jedes einzelne Problem anzuwenden – es ist ein weit verbreitetes Vorurteil, Alles sieht aus wie ein Nagel, wenn man einen Hammer hält.
Sehen wir uns also die verschiedenen Gründe für die Verwendung von Schnittstellen und Protokollen an.
Polymorphismus
Polymorphismus ist genau der Grund, warum Schnittstellen geschaffen wurden. Die häufigsten Beispiele sind Modellklassen, z. Angenommen, Sie haben eine Chat-App und verarbeiten verschiedene Arten von Nachrichten.
Auf diese Weise können Sie die Liste handhaben Message Gegenstände und z.B. sortiere sie nach ihren timestamp aber immer noch in der Lage sein, zwischen Arten von Text- und Bildnachrichten zu unterscheiden. Ohne Schnittstelle müssten Sie auf hässliche Lösungen wie eine Mehrzweckklasse mit vielen Feldern zurückgreifen, die zurückgesetzt werden können.
Mehrfachvererbung
Weder Kotlin noch Swift erlauben Mehrfachklassenvererbung (als Lösung für Diamantproblem), aber seine Vorteile können durch Schnittstellen erreicht werden.
Auf Android, wenn Sie feststellen, dass Sie einigen eine Menge unabhängiger Verpflichtungen hinzufügen BaseStunden, als BaseActivity oder BaseViewModelMöglicherweise möchten Sie ein Upgrade von einfacher Vererbung auf Komposition über eine in Erwägung ziehen Schnittstelle mit Standardimplementierung.
Unter iOS können Sie mithilfe der Protokollerweiterung etwas Ähnliches tun.
In diesen Beispielen können Sie die Kotlina-Erweiterungsfunktion (fun Activity.showToast(...)) und Protokollerweiterungen zu UIView direkt (extension UIView ...), aber die Einführung einer Schnittstelle / eines Protokolls gibt Ihnen mehr Kontrolle – die Funktion ist auf Klassen beschränkt, die davon erben, sodass die Erweiterung nicht an die gesamte Codebasis weitergegeben wird.
Grenzen zwischen Schichten
In seiner Pure Architecture schlägt Uncle Bob eine Struktur vor, die aus bogenartigen Schichten besteht. Die eigentliche Grundlage dieses Paradigmas ist die Regel der Abhängigkeit – die äußeren Schichten können Wissen über die inneren Schichten haben, aber die inneren Schichten sollten nichts über die äußeren wissen.
Reine Architektur ist in der mobilen Entwicklung weit verbreitet, aber eine Sache, die oft missverstanden wird, ist, dass sich die Datenschicht außerhalb der Domänenschicht befindet. Die Geschäftslogik muss nicht wissen, wie Daten geliefert werden, sondern muss nur ihre Eingaben definieren, normalerweise über RepositorySchnittstelledie über eine Datenschicht implementiert werden kann.
In der reinen Architektur definiert die Domänenschicht die Repository-Schnittstelle, die von der Datenschicht implementiert wird
Die Trennung der Geschäftslogik und ihre vollständige Unabhängigkeit von Ansichten und Daten kann große Vorteile haben. Das wird Ihnen jeder sagen, der einen Antrag mit White Labels mit unterschiedlichen Zielen unter Verwendung unterschiedlicher Datenquellen gestellt hat. Wir sollten jedoch die Kosten für übermäßiges Engineering stark berücksichtigen – in vielen mobilen Anwendungen gibt es wirklich nicht viel Geschäftslogik und wir könnten mit einem Haufen trauriger einzeiliger UseCase-Klassen enden, die Daten vom Repository nach ViewModel übertragen.
Dieser Punkt wird erweitert Das hässliche Abschnitt etwas mehr.
Doppeltest
Historisch gesehen waren Schnittstellen unvermeidlich, wenn wir Testduplikate wie haben wollten er spottet ich Fälschungen bei Unit-Tests. Es könnte jedoch argumentiert werden, dass dies eher ein Missbrauch dieser Sprachstruktur als eine legitime Verwendung war. In Java war dies einfach eine Einschränkung des Spott-Frameworks.
In Swift könnte ein protokollbasierter Scheintest so aussehen:
Dies ist jedoch nicht erforderlich. Wir können ein einfaches verwenden class Pro Grinder ich GrinderMock Ich kann davon erben. Ein möglicher Nachteil ist, dass es nicht kompilieren würde, wenn Grinder Er war structDies ist der endgültige Typ, von dem nicht geerbt werden kann.
Ähnlich in Kotlin, in diesem Fall muss die Schnittstelle nicht verwendet werden. Obwohl alle Klassen standardmäßig endlich sind, mögen sich spöttische Frames verspotten oder Mockito kann mit dem endgültigen Typ leicht gehandhabt werden.
Rückrufe
Ein gängiges Muster in der frühen Android-Java-Welt war die Einführung einer Callback-Schnittstelle, die anonym wie folgt implementiert wurde:
Dies ist in Kotlin nicht mehr idiomatisch. Funktionen sind Typen in Kotlin, sodass Sie sie einfach als Variablen weitergeben können
class ListAdapter(val itemClickListener : () -> Unit) { ...
“Weil ich es leicht durch eine andere Implementierung ersetzen kann”
Okay, ich gebe zu, das ist wohl der Hauptgrund, warum ich diesen Artikel überhaupt schreibe. Es ist sehr üblich, diese Antwort zu erhalten, wenn man nach dem Grund für das Schreiben gefragt wird einzige Implementierungsschnittstelle. Manchmal wird es als „Reduzierung von Fusionen“ umformuliert, was sicherlich schlau klingt.
Verstehen Sie mich nicht falsch, die Reduzierung von Fusionen kann sehr sinnvoll sein, z. oben erwähnt Grenzen zwischen Schichten Das Beispiel ist legitim. Aber selbst der Rat von Onkel Bob sollte nicht als Evangelium verstanden werden, jede mögliche Lösung sollte im Kontext des Problems betrachtet werden, das wir zu lösen versuchen.
Daran müssen wir uns alle erinnernclass es hat bereits eine eigene Schnittstelle – dies sind die öffentlichen Methoden, die es präsentiert, und ihre Ergebnisse. Der Schlüssel zum Entwerfen einer guten Schnittstelle ist das Denken in Bezug auf ihre Ein- und Ausgänge, die Verantwortung dieser Klasse. Ein zusätzlicher Hinweis: Unit-Tests sind ein großartiges Werkzeug, um diese Art des Denkens zu fördern.
Am Ende des Tages wollen wir nur einen Wartungscode schreiben. Wie im Zitat zusammengefasst Pragmatischer Programmierer:
Wenn Sie auf ein Problem stoßen, beurteilen Sie, wie lokalisiert die Reparatur ist. Ändern Sie nur ein Modul oder sind die Änderungen über das ganze System verstreut? Wenn Sie eine Änderung vornehmen, wird dadurch alles behoben oder treten auf mysteriöse Weise andere Probleme auf?
Schließen Sie die Schnittstelle von der Implementierung aus formal, mit nur zwei getrennten Typen, ist keine Wunderwaffe, um dies zu erreichen. Getrennt haben interface oder protocol es hat immer seinen Preis – zunehmende kognitive Komplexität, umständlichere Codenavigation usw. Wir sollten immer zwei Dinge bedenken:
wenn es wirklich die Konnektivität reduziert – unsere Schnittstellen können immer noch schlecht gestaltet sein, selbst wenn sie von der Implementierung getrennt sind;
wenn die Reduzierung der Fusion die Kosten wert ist.
Wenn Sie bis hierher gelesen haben und eine einfache Zusammenfassung dieses Artikels wünschen, holen Sie sich diese rote Fahne 🚩: Wenn Sie eine Schnittstelle mit nur einer Klasse entwerfen, die davon erbt, möchten Sie vielleicht innehalten und darüber nachdenken.
Das erfahren wir in diesem Beitrag Proto-DataStoreeiner von zwei DataStore-Implementierungen. Wir werden darüber sprechen, wie erstellen, Daten lesen und schreiben und Ausnahmen behandelnum die Szenarien besser zu verstehen, die Proto zu einer guten Wahl machen.
Proto DataStore verwendet typisierte Objekte, die von unterstützt werden Protokollpufferkleinere Datensätze beim Bereitstellen zu speichern Typ Sicherheit. Dadurch entfällt die Notwendigkeit, Schlüssel/Wert-Paare zu verwenden strukturell anders von seinem SharedPreferences Vorgänger und seine zugehörige Implementierung, DataStore-Einstellungen. Doch das ist noch nicht alles – berichtet DataStore viele weitere Verbesserungen fertig SharedPreferences. Melden Sie sich schnell bei uns zurück erster Beitrag in Folge und schauen Sie sich den detaillierten Vergleich an, den wir dort gemacht haben. Wir werden uns das Folgende ansehen Proto DataStore wie nur Protowenn nicht anders angegeben.
zusammenfassen:
Bietet ein vollständig asynchrone API zum Herunterladen und Speichern von Daten mit der Kraft von Kotlin Korutina
Es bietet keine gebrauchsfertige synchrone Unterstützung – vermeidet direkt jegliche Arbeit, die den UI-Thread blockiert
Es stützt sich auf den internen Fehlersignalisierungsmechanismus von Flow, sodass Sie sicher arbeiten können Ausnahmen abfangen und verarbeiten beim Lesen oder Schreiben von Daten
Verarbeitet sicher Datenaktualisierungen in Atomare Read-Modify-Write-Operationstark zur Verfügung stellen SÄURE Garantien
Erlaubt einfache und einfache Datenmigration
Müssen vollständige Sicherheit und Ihre Daten erfordern die Arbeit mit mehr komplexe Klassen, wie Aufzählungen oder Listen? Dies ist mit nicht möglich Einstellungenalso wähle Proto stattdessen
Um Proto DataStore nutzen zu können, müssen Sie sich kennenlernen Protokollpuffer– sprachneutraler, plattformneutraler Mechanismus für Serialisierung strukturierter Daten. Es ist schneller, kleiner, einfacher und eindeutiger als XML und leichter zu lesen als andere ähnliche Datenformate.
Sie definieren ein Schema aus wie Sie Ihre Daten strukturiert haben möchten und geben Sie Optionen an, z. B. welche Sprache zum Generieren des Codes verwendet werden soll. Der Compiler generiert dann Klassen nach Ihren Vorgaben. Dadurch können Sie problemlos strukturierte Daten schreiben und lesen zu und von verschiedenen Datenströmen, Austausch zwischen verschiedenen Plattformen, Verwendung mehrerer verschiedener Sprachen, z. Kotlin.
Beispielschema einiger Daten ua .proto Datei:
So verwenden Sie den generierten Kotlin-Code zum Erstellen Ihres Datenmodells:
Etwas mehr Zeit in das Erlernen dieses neuen Serialisierungsmechanismus zu investieren, lohnt sich auf jeden Fall, denn er bringt Typsicherheit, verbesserte Lesbarkeit und allgemeine Einfachheit des Codes.
Schauen wir uns jetzt etwas Code an und lernen, wie Proto funktioniert.
Wir werden verwenden Proto-DataStore Codelab-Muster. Wenn Sie an einem praktischeren Ansatz zur Implementierung interessiert sind, empfehlen wir Ihnen wirklich, es durchzugehen Arbeiten Sie mit Proto DataStore Codelab selbstständig.
Diese Beispielanwendung zeigt eine Liste von Aufgaben an, und der Benutzer kann sie nach ihrem abgeschlossenen Status filtern oder nach Priorität und Frist sortieren. Wir möchten ihre Auswahl speichern boolean, um abgeschlossene Aufgaben anzuzeigen ia Aufzählung sortieren Proto.
Wir werden zunächst Proto-Abhängigkeiten und einige der grundlegenden Protobuff-Einstellungen zu Ihrem Modul hinzufügen build.gradle. Wenn Sie an einer fortgeschritteneren Anpassung der Protobuffs-Kompilation interessiert sind, werfen Sie einen Blick darauf Protobuf-Plugin für Gradle-Notizen:
💡 Kurzer Tipp – Wenn Sie Ihre Konstruktion minimieren möchten, fügen Sie unbedingt eine zusätzliche Regel hinzu proguard-rules.pro Datei, um zu verhindern, dass Ihre Felder gelöscht werden:
Unsere Reise mit Proto beginnt mit der Definition Ihrer Struktur persistente Daten u .proto Datei. Betrachten Sie es als lesbares Schema für dich u.a Entwurf für den Compiler. Wir werden unsere nennen user_prefs.proto und füge es hinzu app/src/main/proto Verzeichnis.
Begleitet Protobuf-SprachführerWir werden dieser Datei ein hinzufügen Nachricht für jede Datenstruktur wir wollen serialisieren und dann a spezifizieren Name ia Typ für jedes Feld in der Nachricht. Um dies einfacher zu visualisieren, schauen wir uns sowohl die Kotlin-Datenklasse als auch das entsprechende Protobuff-Schema an.
UserPreferences – Kotlin-Datenklasse:
UserPreferences – .proto planen:
Wenn Sie noch nie Protobuffs verwendet haben, interessieren Sie sich vielleicht für die ersten Zeilen des Schemas. Lassen Sie uns sie aufschlüsseln:
java_package – eine Dateioption, die angibt Paketerklärung für Ihre generierten Klassen, was dazu beiträgt, dies zu verhindern Namenskonflikte zwischen verschiedenen Projekten
java_multiple_files – eine Dateioption, die angibt, ob nur eine einzelne Datei mit verschachtelten Unterklassen werden hierfür generiert .proto (wenn auf false gesetzt) oder if getrennte Dateien wird für jeden Nachrichtentyp der obersten Ebene generiert (wenn auf wahr gesetzt); es ist standardmäßig falsch
Das Folgende ist die Definition unserer Botschaft. Eine Nachricht ist ein Aggregat, das enthält eine Reihe von typisierten Feldern. Viele einfache Standarddatentypen sind als Feldtypen verfügbar, einschließlich bool, int32, floatdoppelt i string. Sie können Ihre Nachrichten auch mit weiter strukturieren andere Nachrichtentypen als Feldtypenwie wir mit SortOrder.
Der = 1, = 2 Die Markierungen auf jedem Element identifizieren das eindeutige “Tag”, das das Feld in der Binärcodierung verwendet – wie z. B. die Sortier-ID. Wenn Ihr Nachrichtentyp verwendet wird, müssen diese Nummern nicht geändert werden.
Wenn Sie den Protokollpuffer-Compiler auf einem ausführen .proto, Der Compiler generiert Code in der von Ihnen ausgewählten Sprache. In unserem speziellen Fall führt dies beim Start des Compilers zur Generierung UserPreferences Klasse, die in Ihrer Bewerbung enthalten ist build/generated/source/proto… Verzeichnis:
💡 Schneller Tipp – Du kannst auch die neu angekündigten ausprobieren Kotlin DSL-Unterstützung für Protokollpuffer um eine idiomatischere Methode zum Erstellen Ihres Datenmodells zu verwenden.
Jetzt, wo wir haben UserPreferenceswir müssen angeben Richtlinien wie Proto sie lesen und schreiben soll. Wir tun dies über den DataStore Serializer es bestimmt das endgültige Format Ihrer Daten wann es gespeichert wird und wie man richtig darauf zugreift. Dies erfordert das Überschreiben von:
defaultValue – was zurückgegeben werden soll, wenn keine Daten vorhanden sind
writeTo – wie man die Speicheranzeige unseres Datenobjekts in ein für die Speicherung geeignetes Format umwandelt
readFrom – die Umkehrung des Obigen, wie man von einem Speicherformat in eine geeignete Speicheranzeige umwandelt
Um Ihren Code so sicher wie möglich zu machen, zu handhaben CorruptionException um unangenehme Überraschungen zu vermeiden, wenn die Datei aufgrund einer Formatbeschädigung nicht deserialisiert werden kann.
💡 Kurztipp – Wenn dein AS mal nichts finden kann UserPreferences in Verbindung gebracht, reinigen und erneuern Ihr Projekt, um mit der Generierung von Protobuff-Klassen zu beginnen.
Sie interagieren mit Proto über Instanzen DataStore<UserPreferences>. DataStore ist eine Schnittstelle, die bietet Zugriff auf dauerhafte Informationenin unserem Fall in der generierten Form UserPreferences.
Es wird empfohlen, zum Erstellen dieser Instanz einen Delegaten zu verwenden dataStore und Pass erforderlich fileName ich serializer Argumente:
fileName verwendet, um a zu erstellen File verwendet, um Daten zu speichern. Deshalb ist es so dataStore Der Delegierte ist a Kotlin-Eigenschaftserweiterung dessen Empfängertyp eine Instanz sein muss Contextweil es benötigt wird, um eine Datei über zu erstellen applicationContext.filesDir. Vermeiden Sie die Verwendung dieser Datei außerhalb von Proto, da dies die Konsistenz Ihrer Daten beeinträchtigen würde.
U dataStore Delegierten können Sie ein weiteres optionales Argument weiterleiten – corruptionHandler. Dieser Operator heißt, wenn a CorruptionException wurde vom Serialisierer ausgeworfen, als Daten können nicht deserialisiert werden. corruptionHandler würde dann Proto anweisen, wie die beschädigten Daten zu ersetzen sind:
Sie sollten nicht mehr als eine DataStore-Instanz für eine bestimmte Datei erstellen, weil es die gesamte Funktionalität des DataStore ruinieren kann. Daher können Sie ein Delegate-Konstrukt einmal auf der obersten Ebene Ihrer Kotlin-Datei hinzufügen und es in der gesamten Anwendung verwenden, um es als zu übergeben Einzelling. Wie das mit einer Suchtspritze geht, sehen wir uns in späteren Beiträgen an.
Zum Auslesen gespeicherter Daten, u UserPreferencesRepository wir belichten a Flow<UserPreferences> von userPreferencesStore.data. Dies ermöglicht ein effizientes Vorgehen der letzte erhaltene Zustand ich Sendungen bei jeder Änderung. Dies ist einer von größte Stärke von Proto – deins Flow‘s Werte kommen bereits in der generierten Form UserPreferences. Das heisst Sie müssen keine zusätzlichen Transformationen vornehmen von den gespeicherten Daten zum Kotlin-Datenklassenmodell, so wie Sie es tun würden SharedPreferences oder Preferences DataStore:
Flow wird immer entweder einen Wert ausgeben oder verwerfen Sie die Ausnahme beim Versuch, von der Festplatte zu lesen. Wir werden uns in späteren Abschnitten mit der Behandlung von Ausnahmen befassen. Der DataStore stellt außerdem sicher, dass die Arbeit immer erledigt wird Dispatchers.IO Ihr UI-Thread wird also nicht blockiert.
🚨 Erstellen Sie keine Cache-Repositories um den aktuellen Stand Ihrer Proto-Daten abzubilden. Damit erlischt die DataStore-Garantie für Datenkonsistenz. Wenn Sie eine Aufnahme Ihrer Daten ohne ein Abonnement für weitere Flow-Shows benötigen, verwenden Sie sie stattdessen userPreferencesStore.data.first():
Wir werden die Aussetzung verwenden, um Daten zu schreiben DataStore<UserPreferences>.updateData(transform: suspend (t: T) -> T) Funktion.
Lassen Sie es uns aufschlüsseln:
DataStore<UserPreferences> Schnittstelle – wird derzeit verwendet userPreferencesStore als konkrete Implementierung von Proto
transform: suspend (t: T) -> T) – ein Suspend-Block, der verwendet wird, um bestimmte Änderungen an unseren permanenten T-Typ-Daten anzuwenden
Auch hier können Sie einen Unterschied bemerken Preferences DataStore die auf den Gebrauch angewiesen ist Preferences ich MutablePreferencesähnlich Map ich MutableMapals Standarddatenanzeige.
Jetzt können wir dies nutzen, um unsere zu ändern showCompleted boolesch. Protokollpuffer vereinfachen dies ebenfallsEliminierung der Notwendigkeit einer manuellen Transformation in und aus Datenklassen:
Es gibt mehrere Schritte für die Analyse:
toBuilder() – er gewann Builder Version von uns currentPreferences die es für Änderungen „freischaltet“.
.setShowCompleted(completed) – setzt einen neuen Wert
.build() – Beendet den Update-Vorgang durch Konvertieren in UserPreferences
Die Datenaktualisierung erfolgt transaktional in einem Atomare Read-Modify-Write-Operation. Das bedeutet, dass es eine bestimmte Abfolge von Datenverarbeitungsvorgängen garantiert, während derer Daten für andere Threads gesperrt sind Konsistenz ich verhindert Rennbedingungen. Nur nach transform ich updateData Corutins erfolgreich abgeschlossen, Daten werden dauerhaft auf der Festplatte gespeichert und userPreferencesStore.data Der Fluss wird die Aktualisierung widerspiegeln.
🚨 Beachten Sie, dass dies die einzige Möglichkeit ist, den Status des DataStore zu ändern. Wartung u UserPreferences Referenz und manuelle Mutation danach transform vervollständigt ändert die permanenten Daten nicht in Proto, also sollten Sie nicht versuchen zu modifizieren UserPreferences außen transform Block.
Wenn der Schreibvorgang aus irgendeinem Grund fehlschlägt, wird die Transaktion beendet und eine Ausnahme ausgelöst.
Wenn Sie es zuvor verwendet haben SharedPreferences in Ihrer Anwendung und Sie Ihre Daten sicher an Proto übertragen möchten, können Sie verwenden SharedPreferencesMigration. Dazu braucht es Kontext, SharedPreferences Namen und Anweisungen, wie Sie Ihren eigenen umwandeln können SharedPreferences Schlüssel/Wert-Paare für UserPreferences Innerhalb migrate Parameter. Geben Sie dies weiter produceMigrations ParameterdataStore Delegierter für einfache Migration:
In diesem Beispiel durchlaufen wir den Konstruktionsprozess UserPreferences und seine Einstellung sortOrder zu dem, was zuvor in der entsprechenden gespeichert wurde SharedPreferences Schlüssel-Wert-Paar oder einfach auf NONE gesetzt.
produceMigrations wird dafür sorgen migrate() wurde gestartet vor einem möglichen Zugriff auf Daten im Datenspeicher. Das bedeutet Ihre Migration ihm muss es gelungen sein bevor der DataStore weitere Werte aussendet und bevor er mit neuen Datenänderungen beginnt. Sobald Sie erfolgreich migriert haben, können Sie die Verwendung sicher einstellen SharedPreferenceswie Schlüssel nur einmal migriert und dann ENTFERNT von SharedPreferences.
Der produceMigrations akzeptiert die Liste DataMigration. Wir werden in späteren Episoden sehen, wie wir dies für andere Arten von Datenmigrationen verwenden können. Wenn Sie nicht migrieren müssen, können Sie dies ignorieren, da es eine gibt Ursprünglich listOf() bereitgestellt schon.
Einer der Hauptvorteile von DataStore gegenüber SharedPreferences ist seine ein geordneter Mechanismus zum Erfassen und Behandeln von Ausnahmen. Während SharedPreferences wirft Parsing-Fehler als Laufzeitausnahmen aus und lässt Raum für unerwartete, nicht abgefangene Abstürze, DataStore wirft aus IOException wenn beim Lesen/Schreiben von Daten ein Fehler auftritt.
Wir können dies sicher lösen, indem wir verwenden catch() Flow- und Broadcast-Betreiber getDefaultInstance():
Oder mit dem einfachen try-catch Blockschreiben:
Wenn eine andere Art von Ausnahme ausgelöst wird, sollten Sie sie besser erneut auslösen.
Wir deckten ab Protokollpufferich Das Proto von DataStore Implementierung – wann und wie man damit Daten liest und schreibt, wie man mit Fehlern umgeht und wie man sie überwindet SharedPreferences. Im nächsten und letzten Beitrag gehen wir noch einen Schritt weiter und sehen, wie sich der DataStore in Ihren einfügt Anwendungsarchitektur, wie man es mit Hilt injiziert und natürlich, wie man es testet. Seh dich später!
Wie bei den meisten akzeptiert Antworten auf SO, werden Sie höchstwahrscheinlich verwenden ActivityManager.getRunningTasks Methode zum Bestimmen, welche Anwendung vorhanden ist Vordergrund Kondition. Für diejenigen, die es nicht wissen Vordergrund ich Hintergrund Android-Anwendungsstatus, hier ist eine kurze Beschreibung,
Für unseren Anwendungsfall Vordergrund dies würde nur bedeuten, dass die Anwendung für den Benutzer nicht sichtbar ist (evtl Dom die Taste wird gedrückt; in diesem Fall wechselt die Anwendung zu Vor kurzem Seite).
Lassen Sie mich auf den Punkt zurückkommen, Ihre erste Beobachtung in offiziellen Dokumenten ActivityManager.getRunningTasks wird sagen, dass die Methode für Android Lollipop (API-Level 21) veraltet ist. ich die der Grund, den Android vorschlägt Es ist ziemlich überzeugend,
Mögen Build.VERSION_CODES.LOLLIPOPDiese Methode steht Anwendungen von Drittanbietern nicht mehr zur Verfügung: Die Einführung neuer dokumentorientierter Methoden bedeutet, dass Anruferinformationen verloren gehen können.
Sie werden auf diese Beobachtung auch stoßen, wenn Sie sich die schrillen Kommentare unter den SO-Antworten ansehen, die die Verwendung von nahelegten ActivityManager.getRunningTasks .
Der Zugriff auf die letzten Aktivitäten von Benutzern ohne Zustimmung kann dazu führen, dass Benutzer glauben, dass Ihre Anwendung eine Art Spyware-Software ist, die auf ihrem Gerät installiert ist. Außerdem könnten Entwickler die Nutzungsmuster von Apps untersuchen, die von Ihrem Gerät gestohlen wurden, um ihre eigenen Apps zu verbessern, alles aus Ihren privaten Daten. Also die Entscheidung zur Aufhebung ActivityManager.getRunningTasks von Android war ein naheliegender Schritt für die Sicherheit der Benutzerdaten.
Wenn Sie nach einem veralteten Ersatz suchen ActivityManager.getRunningTasks Sie werden wahrscheinlich eine Lösung finden, indem Sie verwenden ActivityManager.getRunningAppProcesses Methode. Aber aus irgendeinem Grund gibt die Methode nur den Paketnamen des aktuellen Prozesses zurück, also der Anwendung, die diese Methode aufgerufen hat. Keine andere vom Benutzer verwendete Anwendung wird über die Methode zurückgegeben.
Android scheint diese Methode auch eingeschränkt zu haben, um dem Benutzer eine verbesserte Privatsphäre zu bieten. Außerdem suchen wir nach einem Weg, der mehr ist offiziell und durch Android vor zukünftiger Veralterung geschützt.
Der UsageStatsManager -Klasse bietet Zugriff auf den Gerätenutzungsverlauf und Statistiken. In Anbetracht unseres Anwendungsfalls werden wir spezifische verwenden UsageStatsManager.queryUsageStats eine Methode zum Überprüfen der letzten Benutzeraktivitäten oder kürzlich verwendeten Anwendungen, mit Zustimmung des Nutzers.
Schritt 1: 🔐 Beantragung von Genehmigungen
Offizielle Dokumentation für UsageStatsManager Länder
Die meisten Methoden auf dieser API erfordern eine Genehmigung android.permission.PACKAGE_USAGE_STATS. Das Erklären der Erlaubnis impliziert jedoch die Absicht, die API zu verwenden, und der Gerätebenutzer muss die Erlaubnis dennoch über die Einstellungs-App erteilen.
Es ist also gut, diese Berechtigung zu unserer hinzuzufügen AndroidManifest.xml ,
Clip 1: PACKAGE_USAGE_STATS-Berechtigung zu AndroidManifest.xml hinzufügen
Beachten Sie, dies ist ein Sondergenehmigung und nicht ein Erlaubnis während der Ausführung mögen CAMERA oder WRITE_EXTERNAL_STORAGE die von der Anwendung angefordert werden können. Dies ist offensichtlich, da es sich um eine sensible Erlaubnis zum Zugriff auf die persönlichen Daten des Benutzers handelt, die dem Benutzer bekannt sein muss.
Schritt 2: 🔑 Überprüfen Sie den Status der Genehmigung und suchen Sie danach
In der Logik der Anwendung müssen wir zunächst feststellen, ob dies die vom Benutzer erteilte Erlaubnis ist, und wenn nicht, müssen wir danach fragen. Leider können wir den Status dieser Berechtigung nicht überprüfen ContextCompat.checkSelfPermission weil es sich um eine Sondergenehmigung handelt. Sondergenehmigungen verwaltet er lieber AppOpsManager Dies ist die Klasse, die verwendet wird, um alle Anwendungen zu verwalten und den Zugriff zu steuern.
Clip 2: Überprüfen Sie den Berechtigungsstatus von PACKAGE_USAGE_STATS mit AppOpsManager
Fragen PACKAGE_USAGE_STATS Berechtigungen, die wir nicht verwenden können ActivityCompat.requestPermissions Methode bzw ActivityResultContracts.RequestPermission() weil dies keine Genehmigung während der Ausführung ist, wie wir zuvor besprochen haben. Stattdessen müssen wir den Benutzer zur Seite „Einstellungen“ navigieren, wo der Benutzer diese Anwendungsberechtigung erteilt,
Clip 3: Fordern Sie die Berechtigung PACKAGE_USAGE_STATS an, nachdem Sie den Lizenzstatus überprüft habenNutzungsdateneinstellungen wie auf einem Android 9-Gerät (Samsung J7) beobachtet.
Schritt 3: imanjeLaden Sie das Nutzungsereignis herunter
Sobald der Benutzer die Erlaubnis erteilt, können wir nun auf den Geräteverlauf des Benutzers zugreifen und überprüfen, welche Apps in der Vergangenheit mit Zustimmung des Benutzers verwendet wurden. Jetzt können wir verwenden UsageStatsManager.queryEvents Methode zum Abrufen von Nutzungsereignissen.
Clip 4: Nutzungsereignisse mit der Methode „usageStatsManager.queryEvents“ anfordern.
Wie im Code vermerkt, queryEvents Die Methode benötigt zwei Argumente, beginTime ich endTime . beginTime gibt den Zeitpunkt (in der Vergangenheit) an, ab dem Ereignisse heruntergeladen werden sollen, doc endTime gibt die Zeit an, bis zu der wir Ereignisse benötigen. Beachten Sie, dass beide Argumente sind Unix-Zeit Werte, was beim Gebrauch deutlich wird System.currentTimeMillis() im obigen Code-Snippet.
Der usageEvents.getNextEvent gibt Ereignisse in chronologischer Reihenfolge zurück, also müssen Sie sie sortieren.
Wenn Sie auf Android R-Geräte abzielen, sollten Sie es sich unbedingt ansehen Schritt 5.
Überprüfen Sie die Ausgabe unten, wobei jede Zeile den Paketnamen und den Zeitstempel anzeigt, in dem der Benutzer auf das Paket zugegriffen hat.
Code-Snippet-Ausgabe 4. Die letzte Zeile in der obigen Ausgabe zeigt den Namen des aufrufenden Pakets.
Schritt 4: Vom Benutzer installierte Anwendungen filtern (optional)
Wie Sie sehen können, enthält die obige Ausgabe auch einige Systemanwendungen wie com.sec.android.app.launcher Dies ist die Standardanwendung, die auf Samsung-Geräten ausgeführt wird. Dies liegt daran, dass der Benutzer zum Startbildschirm wechselt, um eine andere Anwendung zu öffnen oder die Geräteeinstellung zu ändern. Möglicherweise möchten Sie diese Systemanwendungen filtern, sodass nur von Benutzern installierte Anwendungen in der Ausgabe sichtbar sind. Wir können eine Methode erstellen, die a zurückgibt Map die vom Benutzer installierte Anwendungen mit ihren Paketnamen und Tags (Anwendungsname, sichtbar für den Benutzer) enthält. Wir können von Benutzern installierte Apps filtern und auch deren Tag abrufen (falls Sie dies in der Logik Ihrer App weiterverarbeiten möchten).
Clip 5: Holen Sie sich einen „Ordner“, der Paketnamen und Tags der vom Benutzer installierten Anwendungen enthält
Schritt 5: Stellen Sie sicher, dass der Benutzer entsperrt ist (mit Android R)
Wenn Sie die Dokumentation auf überprüft haben UsageStatsManager.queryEvent Methode, werden Sie entdecken
Hinweis: Ab Android Rwenn sich das Gerät des Benutzers nicht im entsperrten Zustand befindet (wie definiert durch UserManager#isUserUnlocked()), dann null Wird zurückerstattet.
Mit Android R können wir diese Methode also nicht ausführen, wenn das Gerät gesperrt ist. Wir müssen also sicherstellen, dass der Benutzer entsperrt ist,
Clip 6: Überprüfen Sie, ob der Benutzer mit UserManager entsperrt ist
Denken Sie auch daran UserManager.isUserUnlocked Die Methode ist für API 24 und höher verfügbar, und daher werden wir sie ua bereitstellen if Erklärung.
Dies markiert das Ende unserer Implementierung. Verbinden Sie sich mit einem physischen Gerät und die App funktioniert!
Ich hoffe, dir hat der Blog gefallen! Für Anregungen und Anfragen können Sie gerne einen Kommentar hinterlassen. Lesen Sie weiter, studieren Sie und haben Sie einen schönen Tag!
Screenshot-Tests (oder Screenshot-Tests, ich werde beide Begriffe abwechselnd verwenden) ist eine Art von Tests, die uns hilft sicherzustellen, dass sich unsere Benutzeroberfläche nicht unbeabsichtigt ändert.
Der Testschuss besteht aus 2 Schritten:
Aufzeichnen
Bestätigen Sie
Aufzeichnen
Wenn wir mit der Erstellung eines Teils der Benutzeroberfläche fertig sind, egal ob es sich um einen Vollbildmodus oder nur um eine Schaltfläche handelt, schreiben wir eine Testaufnahme und nehmen auf. Der Screenshot wird in unserem Quellcode gespeichert und definiert die Baseline für unsere Benutzeroberfläche.
Bestätigen Sie
Wir werden höchstwahrscheinlich später auf einen Code eingehen, der die Benutzeroberfläche von unserem ursprünglichen Screenshot ändern kann oder auch nicht. Bei der Überprüfung nimmt die Testaufnahme einen neuen Screenshot auf und vergleicht die beiden Bilder. Wenn sie nicht identisch sind, schlägt unsere Testaufnahme fehl.
“Aber was, wenn ich daran denke, die Benutzeroberfläche zu ändern?” Ruiniert das nicht meine Testaufnahme?“ Ja er wird. Wenn wir die Benutzeroberfläche absichtlich ändern, erfassen wir nur erneut. Als netter Bonus zeigt unsere Auszahlungsanfrage den Look vorher und nachher, was unsere bewerteten Freunde glücklich macht!
Wer hätte gedacht, dass Designer ihre Meinung ändern könnten?
Meiner Erfahrung nach ist das Testen von Videos bei iOS-Entwicklern sehr beliebt, und es wird selten von Android-Entwicklern gesprochen. Ich habe noch nie an Projekten gearbeitet, bei denen dies zuvor für Android durchgeführt wurde. Einer der Gründe könnte sein, dass das iOS-Tool fantastisch und einfach zu starten ist. (Meine Kollegen verwenden https://github.com/pointfreeco/swift-snapshot-testing). Nicht so sehr für Android, obwohl ich denke, dass sich die Dinge verbessern.
In jedem Fall haben wir uns von iOS-Kollegen inspirieren lassen und wollten es selbst ausprobieren. Wir haben auch mit der Arbeit an Jetpack Compose begonnen und dachten, es wäre schön, alle unsere neuen Compose-UI-Komponenten zu testen.
Der erste Schritt war, die richtige Bibliothek zu finden. Von den wenigen Bibliotheken fanden wir nur eine, die Compose erwähnte, also war es eine einfache Wahl: https://github.com/pedrovgs/Shot
Da dieser Blogbeitrag bereits die Grundlagen behandelt, werde ich mich auf die zusätzlichen Schritte konzentrieren, die wir unternommen haben:
Durchführung von Aufnahmetests im Rahmen von CI/CD
Arbeiten Sie mit Screenshots, die auf verschiedenen Emulatorarchitekturen aufgenommen wurden
Machen Sie Tests schnell und einfach zu schreiben
In unserem Projekt verwenden wir Bitrise für CI / CD, aber unser Ansatz sollte auch für andere Einstellungen funktionieren. Mit Shot führen Sie Screenshot-Tests durch die Gradle-Aufgabe durch ./gradlew internalDebugExecuteScreenshotTests wo internalDebug ist Ihre Variante der Herstellung.
Wenn Sie wie wir bereits einige UI-Tests haben, die keine Screenshots sind, müssen Sie etwas Spezifischeres einstellen. Wir tun dies, indem wir eine Namenskonvention verwenden und alle unsere Screenshot-Tests benennen *ScreenshotTest und führen Sie den Build-Befehl wie folgt aus:
Wenn Sie ihn lokal ausführen, wird dieser Befehl auf jedem Emulator oder Gerät ausgeführt, das Sie verwenden. Wenn Sie als Teil Ihres CI/CD arbeiten, müssen Sie zuerst einen Emulator erstellen und ausführen. In Bitrise haben Sie dafür einen netten AVD-Manager-Schritt:
Beim lokalen Erstellen von Screenshots ist es wichtig, denselben Emulatortyp zu verwenden, daher haben wir auch ein Skript zum Erstellen unseres Emulators mit dem entsprechenden Namen bissig. (Dies kann auch in anderen CIs / CDs ohne den AVD-Manager-Schritt verwendet werden):
Dieses Skript ist im Laufe der Zeit etwas gewachsen, um meinen Kollegen dabei zu unterstützen, einen neuen Mac mit Apple-Silikon zu bekommen (dazu später mehr), aber ein wichtiger Teil passiert in der letzten Zeile. Erstellt einen Emulator mit den gleichen Spezifikationen wie der von Bitrise.
Nach dem Schritt AVD Manager in Bitrise müssen Sie warten, bis der Emulator bereit ist, bevor Sie die Screenshot-Tests ausführen. Ich habe eine Weile gebraucht, um herauszufinden, warum es nicht funktioniert hat, da die Fehlermeldung, die Sie erhalten, nicht sehr klar ist. Dazu gibt es natürlich einen Schritt:
Geduld ist eine Tugend
Wir führen dieses Skript lokal aus, um die Screenshots auszuführen (zu überprüfen):
Das Skript zum Erfassen von Screenshots ist sehr ähnlich, jedoch mit dem Capture-Parameter:
Beide verwenden dieses Skript, um den Emulator auszuführen:
Ich habe die Abbruchleitung des Emulators eingeschaltet, als die Tests vorbei waren. Ohne sie würden Sie einen neuen Emulator erhalten, der jedes Mal ausgeführt würde, wenn Sie das Skript ausführen (und Tests würden auf allen gleichzeitig ausgeführt).
Wir zahlen derzeit nicht für die schnellsten Maschinen in Bitrise, daher ist die Pipeline leider zu langsam (25-30 Minuten), um für jede Auszahlungsanforderung ausgeführt zu werden. Das Ausführen der Aufnahmen selbst ist nicht so schlimm, aber das Erstellen und Ausführen des Emulators ist ziemlich langsam. Wenn Sie den Emulator zuvor erstellt und ausgeführt haben, dauert es beim lokalen Start nur 1-2 Minuten, um unsere ~ 50 Tests auszuführen, und diese Zahl erhöht sich nicht wesentlich, wenn neue Tests hinzugefügt werden.
Anstelle jeder Auszahlungsanforderung führen wir die Pipeline jede Nacht aus und senden sie an den Teams-Kanal, wenn sie fehlschlägt. Nicht ideal, aber gut genug:
Vielleicht sollte ich unsere Testaufnahmen reparieren, anstatt Blogbeiträge zu schreiben?
Beim Vergleich von Screenshots, die auf Emulatoren mit unterschiedlichen Architekturen aufgenommen wurden, kann es Unterschiede geben, die für das menschliche Auge nicht sichtbar sind, aber unser Testbild wird trotzdem fehlschlagen. Auf dem neuen Apple-Silikon-Mac meines Kollegen kann kein x86_64-Emulator wie auf meinem älteren Mac oder dem Ubuntu-Docker-Image von Bitrice ausgeführt werden.
Unsere suboptimale Lösung dafür besteht darin, unseren Aufnahmetests etwas Toleranz hinzuzufügen. Wir lassen die Tests erfolgreich sein, wenn der Unterschied zwischen den beiden Screenshots weniger als 2 % beträgt.
Dies kann zu kleinen unbeabsichtigten Änderungen an der Benutzeroberfläche führen, die von Tests nicht erkannt werden. Dies kann ein echtes Problem sein und wir hatten bereits einige Fälle, in denen Screenshot-Tests keine Änderung in der Benutzeroberfläche festgestellt haben:
Ändern Sie ein Symbol in den Vollbildmodus
Es verändert die Marge ein wenig
Das Schreiben von Filmmaterialtests kann mühsam und repetitiv sein, daher möchten wir, dass sie einfach zu schreiben sind und sich nicht wie eine Verpflichtung anfühlen.
Mit Jetpack Compose erstellen wir regelmäßig Reviews unserer Komponenten, die unterschiedliche Konfigurationen zeigen.
Wenn wir Footage-Tests erstellen, wollen wir das Gleiche. Also haben wir angefangen, Testaufnahmen unserer Bewertungen zu erstellen:
Mit der Philosophie, für jede Rezension einen Screenshot-Test zu machen, können wir sehen, dass alle Screenshots sehr ähnlich aussehen werden.
Fürs Erste haben wir eine Live-Vorlage in Android Studio erstellt, um sie schneller zu schreiben.
Schreiben Sie einfach einen Snap, drücken Sie auf die Registerkarte und geben Sie den Namen Ihrer Bewertung ein. Dies ist schneller, als sie jedes Mal von Hand zu schreiben, aber eine zusätzliche Verbesserung könnte darin bestehen, ein Skript zu erstellen, das einen Test für jede Überprüfungsnotiz generiert.
Das Testen der Aufzeichnung ist für Android immer noch etwas umfangreicher als für iOS, und die von uns verwendete Bibliothek hat keine großartige Integration mit Android Studio. Sie können einen Screenshot-Test nicht einfach mit der Wiedergabetaste ausführen / aufzeichnen, Sie müssen die Gradle-Aufgabe verwenden.
Der HTML-Bildverifizierungsbericht zeigt Ihnen, wenn Änderungen an Ihrer Benutzeroberfläche vorgenommen werden
Aber für mich machen diese Vorteile es immer noch wertvoll:
Wir können sicher sein, dass sich unsere Benutzeroberfläche nicht unbeabsichtigt ändert
Unsere Auszahlungsanträge erhalten nette Bewertungen der neuen UI, die wir vorgenommen haben, und der UI-Änderungen, die wir “kostenlos” vorgenommen haben.
Wir können aufhören, die Art von Espresso-Tests zu schreiben, die prüfen, ob alle Elemente der Benutzeroberfläche auf dem Bildschirm angezeigt werden
Ich werde das nicht vermissen
Hinterlasse einen Kommentar oder kontaktiere mich, wenn du einen Snapchat haben möchtest. Wenn Sie Fragen oder Verbesserungsvorschläge zu unserem eigenen Setup haben oder Hilfe beim Einrichten für Ihr eigenes Projekt benötigen. Hier oder auf Twitter: https://twitter.com/andersullnass.
Danke an Alex von der niederländischen Android-Slack-Benutzergruppe (https://twitter.com/alex_caskey) für die großartige Hilfe, als ich dies ursprünglich gepostet habe, und vielen Dank an meinen Kollegen Cristan, der mit mir daran gearbeitet hat, die Art und Weise, wie wir die Aufnahmen testen, zu verbessern, und für die Überprüfung dieses Blog-Beitrags.
Die Wahl eines Geräts ist nie so einfach wie es ist, aber das neue Apple Silicon SoC für Mac hat den Markt ordentlich erschüttert und die Wahl noch schwerer gemacht. Welches MacBook soll ich kaufen? Wie unterschiedlich sind sie? Es ist schwer zu verstehen. Außerdem ist die Suche nach detaillierten Tests eine Höllenarbeit.
In diesem Artikel gebe ich Ihnen korrekt Vergleich der Build-Geschwindigkeiten anhand einiger realistischer Android-Projekte und einer Reihe verschiedener MacBook-Modelle. Außerdem sage ich Ihnen, welche Sie bis 2022 kaufen sollten.
In diesem Test habe ich die neuesten Intel-betriebenen MacBook Pro-Laptops sowie einige der neuesten MacBooks mit verwendet M1.
Ich habe Ende 2015 meinen alten iMac 27″ hinzugefügt, um zu testen, wie weit die neuen Mac-Modelle fortgeschritten sind.
Es ist nicht einfach sicherzustellen, dass alle Computer unter den gleichen Bedingungen funktionieren, aber ich habe versucht, perfekt zu sein:
Verwenden der neuesten Version von JDK 11. Für Apple Silicon habe ich Zulu ARM JDK verwendet
Verwenden von Gradle 7.3.3 zum Erstellen von Projekten (neueste Version zum Zeitpunkt des Tests verfügbar)
Schalten Sie externe Displays aus
Schließen Sie alle Computer an eine Stromquelle an
Schließen Sie so viele Programme wie möglich, auch die im Hintergrund
Schließen Sie Android Studio!
Versetzen Sie den Computer in den Ruhemodus (falls verfügbar)
Stellen Sie alle Laptops auf eine harte, ebene Oberfläche, damit der Luftstrom eindringen und den Laptop kühlen kann
Berühren Sie Computer während der Tests nicht
Deaktivieren Sie die Spotlight-Indizierung oder fügen Sie Testprojektordner zu Ausnahmen hinzu.
Wenn Sie weitere Bedingungen kennen, die vor dem Benchmark-Test berücksichtigt werden müssen, lese ich sie gerne in den Kommentaren unten.
Ich habe Tests mit durchgeführt Gradle-Profiler im Benchmark-Modus, der die Zeit misst, die zum Erstellen eines Projekts erforderlich ist. Die Ergebnisse, die Sie sehen werden, sind die Mittelwerte von zehn Testläufen, denen zwei Aufwärmübungen vorausgegangen sind, die nicht gewertet haben.
Ich habe alle getesteten Projekte Lagerund jedes Projekt hat sein eigenes Startup-Testszenario (a performance.scenarios Datei).
Die genauen Ergebnisse finden Sie in der folgenden Tabelle.
TiVi 0.6.3
TiVi ist eine kleine TV-Show-Tracking-Anwendung, die auf basiert trakt.tv.
Beckencodezeilen: 15K Module: 25
Dieses Projekt ist recht klein, aber auch hier hinkt Intel dem M1 deutlich hinterher. Der M1 Pro / Max ließ den M1 nur eine Sekunde hinter sich. Diese Tests zeigen uns eines deutlich: Schon bei einem kleinen Projekt wie diesem sieht man, dass Apple Silicon deutlich schneller ist als der Intel Core 9 der 9. Generation.
TiVi 0.6.3 Benchmark-Ergebnisse
ExoPlayer r.2.16.1
ExoPlayer ist ein beliebter Mediaplayer auf Mediaplayer-Ebene für Android.
Java-Codezeilen: 225.000 Module: 35
Dieses nächste Projekt demonstriert die Vorteile einer Reihe von Hochleistungskernen im M1 Pro / Max. Die Ergebnisse zeigen einen deutlichen Vorsprung: Der M1 hatte ein Ergebnis von 39,4 Sekunden, während der M1 Pro / Max 10 Core 28 Sekunden erreichte. Währenddessen erzielte Intels bester Chip 43,9 Sekunden.
ExoPlayer r.2.16.1 Benchmark-Ergebnisse
Fokus 95.2.0
Fokus ist eine sichere Version von Firefox, die sich auf den Datenschutz konzentriert.
Die Kriterien für Focus zeigen bereits deutliche Muster. Allerdings zeigen sie auch, dass die M1 Pro 8 Core Version nicht wesentlich hinter der Vollversion des M1 Pro oder M1 Max zurückbleibt.
Die Kompilierung von Kotlin-Code ist im Vergleich zu Java langsamer. Dennoch zeigen Apple-Prozessoren eine beständige Steigerung der Kompilierungsgeschwindigkeit. Das einzige ist, dass Sie immer noch keinen Unterschied zwischen M1 Pro 8-Kern- und 10-Kern-CPUs sehen können. Was?
Firefox 95.2.0-Benchmark-Ergebnisse
Signal 5.28.8
Signal ist eine beliebte Alternative zum Telegramm.
Schließlich kann man sehen, dass die 8 Kerne im M1 Pro langsamer sind als die Standardversion dieses Prozessors. Darüber hinaus unterstützt es nur die Schlussfolgerungen, die aus früheren Projekten gezogen wurden.
Benchmark-Ergebnisse Signal 5.28.8
Telegramm 8.2.1
Telegramm Android GitHub ist ein beliebter Bote. Dort ist der Code ziemlich ungewöhnlich: Es gibt ein NDK und die gesamte Benutzeroberfläche ist in Java-Code, es gibt kein Basin und es gibt kein apt oder kapt, um den Code zu generieren.
Telegram Android ist das größte Projekt, das ich getestet habe, und zeigt deutlich den Unterschied zwischen den Prozessoren. Die Benchmarks waren verblüffend: Es ist das einzige Projekt, das ich getestet habe, bei dem der i7 und i9 in der MacBook Pro-Version 2019 das Projekt schneller machen konnten als der Apple M1 im MacBook Air. Natürlich musste ich wissen, warum es passiert ist.
Telegramm 8.2.1 Benchmark-Ergebnisse
Es stellt sich heraus, dass der Grund dafür CMake und die ursprünglichen Kompilierungstools sind, von denen es viele in Telegram gibt. Apple M1 führt die Kompilierung mit aus Rosette 2. Während ich dies schreibe, unterstützt Android NDK Apple Silicon immer noch nicht. CMake (ab Version 3.19) und andere Tools unterstützen es jedoch bereits. Sie können die Einzelheiten lesen Hier.
Im weiteren Verlauf werden wir also einen deutlichen Schub erhalten: Der Apple M1 hat zumindest die Chance, den i7 und (lasst uns wild träumen!) sogar den i9 zu übernehmen. Warten wir also auf das fertige Werkzeug. Der Test zeigt auch deutlich, dass das M1 Pro / Max so unoptimierte Software ausführt, dass es sogar in der Lage ist, das 9. i9-Gen herunterzuladen.
Benchmark-Ergebnisse
Sie können Intel Core nicht abschreiben, wenn Ihr Projekt viel Quellcode enthält. Übrigens gibt es keinen Grund, Intel-Prozessoren zu verwenden: Sie werden sogar von den einfachsten Apple M1 übertroffen. Mit der Zeit werden die Tools fertig sein und die Vorteile des Besitzes eines Apple-Prozessors werden noch offensichtlicher.
Ein MacBook Air mit einem M1 würde für die meisten mobilen Entwickler ausreichen. Trotzdem würde ich empfehlen, das mit 16 GB RAM zu kaufen. Auf diese Weise können Sie Anwendungen für Android und iOS erstellen und die Entwicklung auf mehreren Plattformen (Kotlin Multiplatform, Flutter) ausprobieren. Ein riesiger Vorteil ist, dass es keinen Kühlschrank gibt, wodurch es ruhig, aber manchmal ziemlich heiß wird (wenn der Bau besonders lange dauert).
Wenn Sie an großen Projekten arbeiten und maximale Funktionen oder über 16 GB RAM wünschen, würde ich empfehlen, sich das MacBook Pro mit der M1 Pro 10-Core-CPU genauer anzusehen. Das 16-Zoll-Modell wird schneller und leiser sein.
Der M1 Max und der M1 Pro zeigten keinen signifikanten Unterschied in den Testergebnissen. Wenn Sie Entwickler sind, lohnt es sich, die Max-Version nur zu kaufen, wenn Sie 64 GB RAM benötigen oder mehr Bedürfnisse als die Entwicklung haben, wie z. B. Videobearbeitung oder die Arbeit mit 3D-Grafiken. Und es lohnt sich, nur ein 16-Zoll-Modell zu kaufen – die 14-Zoll-GPU hat eine niedrigere FPS, und der Prozessor wird viel früher heruntergefahren und wird viel lauter, wenn er überhitzt. Denken Sie auch daran, dass das 14-Zoll-Modell einen kleineren Akku hat, und selbst wenn Sie nichts Kompliziertes tun, ist die Akkulaufzeit im Vergleich zum 16-Zoll-Modell kürzer.
Ich würde gerne weitermachen und die Kompilierungsleistung auf High-End-Prozessoren von AMD und Intel vergleichen, aber es gibt immer noch keine Möglichkeit, dies zu tun. Die Tests habe ich bereits gemacht Ryzen 5950X mit Linux, und es ist extrem schnell! Die erste synthetische Waage für I9 12900HK sie versprechen! Aber das ist das Thema für einen anderen Artikel …
Jetpack Compose ist ein moderner Satz von Tools für Android zum Erstellen einer nativen Benutzeroberfläche. Vereinfacht und beschleunigt die Entwicklung der Benutzeroberfläche auf Android. Beleben Sie Ihre App schnell mit weniger Code, leistungsstarken Tools und intuitiven Kotlin-APIs.
Ich habe kürzlich Jetpack Compose gelernt und fast sofort den Wahnsinn dahinter erkannt. Mit Compose können Sie im Vergleich zur Verwendung von Android View mit weniger Code mehr erreichen: Schaltflächen, Listen oder Animationen – was auch immer Sie tun müssen, Sie müssen jetzt weniger Code schreiben.
Ich habe ConstraintLayout oft in meinen Projekten verwendet. Als ich zu Compose wechselte, hatte ich natürlich das Bedürfnis, es auch hier zu erkunden. Hier sind also die Entwürfe, die wir umsetzen werden:
gehen MainActivity.kt und ersetzen Sie den Code durch:
Erstellen Sie eine neue Klasse: MovieBookingScreen.kt. Und fügen Sie den folgenden Code hinzu:
Hier erstellen wir zunächst eine grundlegende Compositing-Funktion mit nichts als a Surface ia ConstraintLayout die die gesamte Höhe und Breite abdeckt.
Zum Vergleich mit einem herkömmlichen XML-basierten ConstraintLayout weisen wir unseren Ansichten IDs zu und verwenden diese IDs dann, um andere Ansichten entsprechend einzuschränken. Und hier erstellen wir zuerst all diese Referenzen mit createRefs()Funktion und ordnen Sie diese dann unseren Komponenten zu.
Nehmen wir zunächst das Menüsymbol in der oberen linken Ecke. Wir gebrauchen constrainAs(reference) um dem Komponierten eine Referenz zuzuweisen. Und dann u constrainBlock, schreiben wir alle Einschränkungen. Im folgenden Beispiel weisen wir zu menuButton Unter Bezugnahme auf das kompatible Symbol definieren wir in constrainBlock seine Einschränkungen:
Anfang ich oben verbunden sein soll ElternAnfang ich oben das heißt mit 16 dps-Margen
Anstelle des übergeordneten Elements können wir auf andere zusammengesetzte Referenzen verweisen.
Richtlinie
Die Richtlinien in ConstraintLayout sind unsichtbare Linien, die für Benutzer nicht sichtbar sind, aber Entwicklern dabei helfen, das Layout einfach zu gestalten und die Ansicht dieser Richtlinien einzuschränken, sodass das Design klarer und interaktiver sein kann.
Nehmen wir als Nächstes an, wir möchten eine Richtlinie auf 40 % erstellen und das Titelbild des Films zwischen der übergeordneten und der Richtlinie begrenzen. Wir möchten auch sicherstellen, dass die Breite und Höhe des Bildes 2: 3 betragen.
Wir können Folgendes tun, indem wir zuerst eine Richtlinie erstellen. (40 % werden als 0,4f übertragen). Wir können auch den absoluten Wert weitergeben dp. Dann definieren wir die Breite als Dimension.fillToConstraints das ist analog zum tunen android:layout_width=”0dp” . Wir verwenden, um das Verhältnis einzustellen aspectRatio Modifikator, wo wir das Verhältnis übertragen. Das ist wieder analog app:layout_constraintDimensionRatio=”2:3" .
Ebenso können wir andere Texte erstellen, die komponiert und begrenzt werden können.
Horizontale Verkettung
Eine Kette ist eine Gruppe von Ansichten, die durch bidirektionale Positionsbeschränkungen miteinander verbunden sind
Die Kette kann von 3 Arten sein:
Spread: Ein Kettenstil, der die enthaltenen Layouts gleichmäßig verteilt.
SpreadInside: Ein Kettenstil, bei dem die erste und die letzte Anordnung an die Beschränkungen an jedem Ende der Kette angehängt werden und der Rest gleichmäßig verteilt wird.
Packed: Der Kettenstil, in dem die Zeitpläne enthalten sind, wird zusammengepackt und in der Mitte des verfügbaren Platzes platziert.
Macht einen Teil unseres Looks aus
Als Nächstes erstellen wir für die Besetzungslinie zunächst ein ConstraintLayout, fügen unsere zusammengesetzten hinzu und führen sie dann mit zusammen createHorizontalChain(). Außerdem ist jedes Bild, das zusammengesetzt werden kann, durch die Angabe eines Verhältnisses von begrenzt 1:1 um eine quadratische Form beizubehalten.
Barriere
Die Barriere verweist auf mehrere Widgets als Eingabe und erstellt eine virtuelle Richtlinie basierend auf dem extremsten Widget auf der angegebenen Seite. Beispielsweise wird die linke Barriere links von allen referenzierten Ansichten ausgerichtet.
Eine Barriere kann wie unten gezeigt einfach erstellt werden, indem Sie zu den Ansichten gehen. Hier schaffen wir die untere Barriere, um unter dem Titelbild und unserem Gussbehälter Orientierung zu bekommen. In ähnlicher Weise können wir eine obere, untere, anfängliche und endgültige Barriere erstellen.
val barrier = createBottomBarrier(coverImage, castContainer)
Als nächstes begrenzen wir den Text der Beschreibung unterhalb der Barriere. Damit sind wir mit der oberen Hälfte unseres Looks fertig 🎉. Jetzt bleibt uns die untere Hälfte des Bildschirms.
Teilen Sie die untere Hälfte des Bildschirms in 3 Teile:
Kalenderleiste
Details zum Kino
Schaltfläche “Tickets buchen”.
Erstens kann ein grauer Hintergrund implementiert werden, indem eine einfache zu komponierende Oberfläche hinzugefügt und ihr eine graue Farbe gegeben wird.
1. Kalenderleiste
Die Kalenderleiste kann wiederum in drei Teile unterteilt werden: Voter, Datumsreihenfolge und Tagesreihenfolge.
Zuerst erstellen wir Text Composables für Tagesreihen und begrenzen Sie sie mit einer horizontalen Kette, um sie gleichmäßig über die Breite des Bildschirms zu verteilen. Dann erstellen wir Text Compoasables für die Datumslinie und begrenzen jedes Datum auf den entsprechenden Tag (Start und Ende) und das vorherige Datum (oben und unten), etwa so:
Jetzt kommt der interessante Teil. Wir erstellen zwei für die Datumsauswahl Surface zusammensetzbar. Ein weißer Streifen, der den Haupthintergrund darstellt, und der andere schwarze Streifen, der auf die weiße Fläche beschränkt ist. Da wir nun möchten, dass die Datumsauswahl dynamisch ist und sich zum ausgewählten Datum bewegt, speichern wir sie im Status, anstatt die Einschränkungen für den Beginn und das Ende des ersten Tages fest zu codieren. Wann immer wir also den Wert eines Zustands ändern, a recomposition wird ausgeführt, und unsere Datumsauswahl ändert ihre Grenzen entsprechend.
Sehen Sie sich als Beispiel den Composable-Clip unten aus dem Text „Montag“ an. U clickable Block ändern wir den Wert der Einschränkung des Datumsauswahlbereichs.
2. Kinodetails
Der Name des Kinos und die Entfernung können als zwei zusammengesetzte Texte betrachtet werden, die in der Mitte des verfügbaren Raums zwischen Kalenderleiste und Schaltfläche verbunden sind. Also erstellen wir einen ConstraintLayout-Compiler neu, der den gesamten verfügbaren Platz einnimmt, und verknüpfen beide Texte, die kompiliert werden können, mit VerticalChainBeibehaltung des Kettenstils als Packed.
3. Schaltfläche „Tickets buchen“.
Dies ist nur eine einfache Schaltfläche, die mit Text komponiert werden kann, der darin komponiert werden kann. Es ist auf die Unterseite des Elternteils begrenzt und so konzipiert, dass es die gesamte Breite einnimmt.
Und das ist es 😎
Folgendes haben wir erreicht:
Hier ist der vollständige Code MovieBookingScreen.kt für ihre referenz:
Danke fürs Lesen. Ich würde gerne alle Ihre Probleme besprechen und lösen. Wenn dir der Artikel gefallen hat, applaudiere und teile ihn 🙃
iOS und Android: Diese Module enthalten iOS- und Android-Anwendungen (Xcode- und Android Studio-Projekte). Der Großteil des Codes sollte nur aus SwiftUI und ComposeUI bestehen.
Kotlin teilt den Code: Dies ist ein Modul, das das KMM-Projekt enthält, hier werden wir unsere Geschäfts- und Präsentationslogik implementieren. Der Kern unserer App wird also hier sein.
Zwiebel: Dies ist die KMM-Bibliothek, die ich erstellt habe und die wir als Abhängigkeit zu unserem gemeinsamen Code hinzufügen werden.
Bogen ist eine kleine und leichtgewichtige Kotlin-Multiplattform-Bibliothek, die uns bei der Architektur mobiler Anwendungen hilft, sie basiert auf der Spotify Mobius-Bibliothek, aber anstatt sich auf RxJava zu verlassen, verlässt sie sich auf Roots, SharedFlow ich StateFlow
Wir werden die grundlegenden Konzepte der Bibliothek wiederholen, ich werde einige Themen auslassen, wie z Veranstaltungen und Umgang mit Fehlern nur der Einfachheit halber.
Hauptmerkmale:
Fluss in eine Richtung
Einfache Zustandsverwaltung
Jetpack-Unterstützung ViewModel (unter Verwendung des tatsächlichen / erwarteten Protokolls)
Anwendungsablauf mit Arch
Dieses Diagramm zeigt, wie die Komponenten in dieser Architektur interagieren, schauen wir uns jede von ihnen an und geben eine kurze Erklärung:
Aktionen: action ist eine Anforderung, den Status der Anwendung (oder eines Teils davon) zu ändern, Aktionen werden von iOS- und Android-Code gesendet.
Aktualisieren: Sie können sich das Update als reine Funktion vorstellen, die die Aktion und den aktuellen Zustand entgegennimmt und den neuen Zustand wiederherstellt. Neben der Änderung der Bedingung hat Updater Ich kann senden Nebenwirkungen ich Veranstaltungen
Nebenwirkungen: Im Allgemeinen ist ein Nebeneffekt eine Operation, die einen Zustand außerhalb seiner lokalen Umgebung in unserer Architektur ändert Nebenwirkungen sind Operationen wie HTTP-Anforderungen, Datenbanktransaktionen oder beliebige I/O-Operationen.
Prozessor: der Prozessor ist das Element, das er verarbeitet Nebenwirkungen ist für die Ausführung einer Datenbanktransaktion, einer HTTP-Anfrage oder irgendetwas Empfangenem verantwortlich Nebenwirkung solltest. Wenn die Aufgabe abgeschlossen ist (erfolgreich oder nicht), sendet der Prozessor eine neue Aktion um den Status zu aktualisieren.
Lassen Sie uns in den eigentlichen Code eintauchen, wir werden eine sehr einfache Anwendung für Filme mit dieser vorgeschlagenen Architektur schreiben film db api
Schritte
Definiere den Zustand
Definieren Aktionen
Nebenwirkungen definieren
Erstellen Sie ein Ansichtsmodell
Implementieren Updater
Implementieren Prozessor
Beobachten Sie die Zustandsänderungen in den Ansichten SwiftUI und ComposeUI.
Mit Ausnahme des letzten werden alle diese Schritte in unserem gemeinsamen Valley-Modul implementiert.
Zustand
Für diese App müssen wir nur zwei Dinge in unserem Zustand speichern:
Liste der Filme
Ausgewählter Film, da wir die Details des Films anzeigen möchten, wenn der Benutzer ihn in der Liste berührt.
Aktionen
Wir brauchen nur drei Aktionen:
Die ersten beiden Aktionen werden an Benutzerereignisse gesendet (z. B. Berühren oder Öffnen einer App), die letzte wird gesendet Prozessor wenn wir eine Antwort von der API erhalten.
Nebenwirkungen
Wir haben eine Single Nebenwirkung: Holen Sie sich eine Liste von Filmen. Wir könnten festlegen, auf welcher Reihe von Dispatchern oder Cornets wir laufen möchten Nebenwirkungwird standardmäßig verwendet viewModelScope auf Android und benutzerdefinierter Bereich auf iOS
Updater
Hier entscheiden wir wie Wir wollen den Zustand basierend auf der empfangenen Aktion ändern, wir müssen eine Klasse erstellen, die implementiert Updater Schnittstelle, die nur eine Methode hat, die einen neuen Zustand und (vielleicht) einen neuen zurückgibt Nebenwirkungen eingewickelt in a Nächste Objekt.
Beachte das Filme abrufen () Methode gibt a zurück NächstesErgebnis ein Objekt, das a enthält Nebenwirkung, besonders Filme laden eins. So Prozessor werden über diesen neuen Beitrag benachrichtigt Nebenwirkung und führt eine HTTP-Anforderungsoperation aus
Prozessor
Jetzt müssen wir umsetzen Prozessor Schnittstelledie eine einzigartige Suspend-Funktion hat, die die Aktion wiederherstellt.
TMDApi Klasse ist dafür verantwortlich, HTTP-Anforderungen zu erstellen, um Filme abzurufen, und json-Antworten in eine Reihe von Filmen umzuwandeln. Es ist uns egal, wie es implementiert wird, aber wenn Sie neugierig sind, können Sie sich jederzeit das vollständige Democode-Repository unter der Haube ansehen von Ktor verwendet.
Sobald wir die Liste der Filme erhalten haben, senden wir einfach eine Aktion, um sie im Status zu speichern.
ViewModel
Wir haben es fast geschafft, jetzt müssen wir all diese Klassen, die wir implementiert haben, zusammenführen ViewModel die erben müssen ArchViewModel
Ja! das ist unser ganzer Code ViewModel braucht.
Beachten Sie, dass wir den Anfangszustand und den Anfangseffekt eingestellt haben.
Beobachten Sie Änderungen in der Situation
Der letzte Schritt, beobachten Sie einfach die Zustandsänderung! aber wie?
ArchViewModel entlarvt a Fließen die alle Zustandsänderungen ausgibt, also müssen wir sie nur sammeln.
Auf Android sammeln a Fließen ist einfach, aber nicht so einfach auf iOS, daher implementiert die Arch-Bibliothek Folgendes FlowWrapper
Wenn Sie eine bessere Lösung für dieses Problem des Sammelns von Kotlina haben Fließt aus Swift-Code, ich würde ihn gerne lesen.
Schauen wir uns den endgültigen Code an:
Und das ist alles! Denken Sie daran, dass es sich um einen vollständigen Code handelt Hier
Aus der Perspektive von Android-Entwicklern ist die Entwicklungserfahrung mit KMM (und diesem Ansatz) so ziemlich die gleiche wie bei der reinen Entwicklung von Android. Aber für iOS-Entwickler ist die Erfahrung nicht so gut wie für Android-Entwickler, auf iOS müssen wir uns um Dinge wie Gleichzeitigkeit kümmern, Unveränderlichkeitsausnahme und andere Inkompatibilitätsprobleme, die auftreten können.
Jetbrains ist sich dieser Probleme bewusst und arbeitet daran, das Erlebnis für iOS-Entwickler zu verbessern. Am 31. August 2021 haben sie eine Vorschauversion veröffentlicht neuer Speichermanager Dies sollte uns von der Notwendigkeit befreien, Artikel einzufrieren.
Diese Architektur eignet sich hervorragend für Teams, die große native Apps für iOS und Android verwalten, insbesondere wenn die Entwickler in Ihrem Team über Kenntnisse beider Plattformen verfügen.
Normalerweise haben iOS- und Android-Versionen derselben Anwendung unterschiedliche Architekturen wie MVC, MVP, MVVM, VIPER oder Redux. Die Beibehaltung derselben Architektur auf beiden Plattformen hat viele Vorteile, erfordert weniger Arbeit und hilft, Aufgaben besser auf Ihr Team zu verteilen, sogar Entwickler können dies tun sehr einfach auf beiden Plattformen funktionieren.
Alles in allem glaube ich nicht, dass es die perfekte Lösung gibt, jedes Team muss sich überlegen, welche für seine Bedürfnisse am besten geeignet ist.
Wir werden uns diesen Beitrag ansehen DataStore-Einstellungeneiner von zwei Datenspeicher Implementierung. Wir werden weitermachen, wie erstellen, Daten lesen und schreiben und Ausnahmen behandelnund all das sollte Ihnen hoffentlich genügend Informationen liefern, um zu entscheiden, ob dies die richtige Wahl für Ihre Anwendung ist.
Einstellungen DataStore-Verwendungen Schlüssel/Wert-Paare um kleinere Datensätze zu speichern, ohne zuerst das Schema zu definieren. Dies könnte Sie daran erinnern SharedPreferences, aber nur in der Art und Weise, wie es Ihre Datenmodelle strukturiert. Es gibt ein verschiedene Vorteile bringt der DataStore durch seine SharedPreferences Vorgänger. Melden Sie sich schnell bei uns zurück vorherigen Postund schauen Sie sich den detaillierten Vergleich an, den wir dort gemacht haben. Wir werden uns das Folgende ansehen Preferences DataStore wie nur Preferenceswenn nicht anders angegeben.
Für eine kurze Zusammenfassung:
Bietet ein vollständig asynchrone API zum Herunterladen und Speichern von Daten mit der Kraft von Kotlin Korutina
Es bietet keine gebrauchsfertige synchrone Unterstützung – vermeidet direkt jegliche Arbeit, die den UI-Thread blockiert
Es stützt sich auf den internen Fehlersignalisierungsmechanismus von Flow, der Ihnen dies ermöglicht Ausnahmen sicher erfassen und verarbeiten beim Lesen oder Schreiben von Daten
Verarbeitet Transaktionsdatenaktualisierungen in einer Atomares Lesen-Modifizieren-Schreiben Arbeit, Bereitstellung stark SÄURE Garantien
Erlaubt einfache und schnelle Datenmigration
Wollen schnell migrieren von SharedPreferences mit minimale Änderungen und Sie fühlen sich ohne volle Sicherheit sicher genug? Wählen Einstellungen über Proto
Lassen Sie uns nun in etwas Code eintauchen und lernen, wie Einstellungen implementiert werden sollten.
Diese Beispielanwendung zeigt eine Liste von Aufgaben an, und der Benutzer kann sie nach ihrem abgeschlossenen Status filtern oder nach Priorität und Frist sortieren. Wir möchten ihre Auswahl speichern boolean für abgeschlossene Aufgaben ia Aufzählung sortieren im Datenspeicher.
Beginnen wir mit dem Hinzufügen der erforderlichen Abhängigkeiten:
💡 Ein kurzer Tipp: Wenn Sie Ihre Konstruktion minimieren möchten, fügen Sie Ihrer eigenen eine zusätzliche Regel hinzu proguard-rules.pro Datei, um zu verhindern, dass Ihre Felder gelöscht werden:
Sie interagieren mit den im DataStore gespeicherten Daten über eine Instanz DataStore<Preferences>. DataStore ist eine Schnittstelle, die den Zugriff auf dauerhafte Informationen gewährt. Einstellungen ist eine abstrakte Klasse ähnlich einer generischen Karte, die insbesondere in der Implementierung des Preferences DataStore verwendet wird, um Schlüssel-Wert-Paare Ihrer Daten zu verfolgen. Wir werden darüber sprechen MutablePreferences Unterklasse, wenn wir über das Schreiben von Daten sprechen.
Es wird empfohlen, zum Erstellen dieser Instanz einen Delegaten zu verwenden preferencesDataStore und Pass erforderlich name Streit. Dieser Delegierte ist a Kotlin-Eigenschaftserweiterung dessen Empfängertyp eine Instanz sein muss Contextanschließend für den Bau benötigt File Objekt, in dem DataStore Daten speichert:
Sie sollten nicht mehr als eine DataStore-Instanz für eine bestimmte Datei erstellen, weil es die gesamte Funktionalität des DataStore ruinieren kann. Daher können Sie ein Delegate-Konstrukt einmal auf der obersten Ebene Ihrer Kotlin-Datei hinzufügen und es in der gesamten Anwendung verwenden, um es als zu übergeben Einzelling. Wie das mit einer Suchtspritze geht, sehen wir uns in späteren Beiträgen an.
Schlüssel definieren
DataStore bietet eine schnelle Möglichkeit, Schlüssel für verschiedene Datentypen zu erstellen, z booleanPreferencesKey, intPreferencesKey und vieles mehr – Sie müssen nur bestehen Schlüsselname als Wert. Obwohl dies einige Einschränkungen für Datentypen mit sich bringt, sollten Sie dies im Hinterkopf behalten bietet keine endgültige Typsicherheit. Indem wir den bevorzugten Schlüssel eines bestimmten Typs bestimmen, hoffen wir das Beste und verlassen uns auf unsere Annahmen der Wert eines bestimmten Typs würde zurückgegeben. Wenn Sie der Meinung sind, dass Ihr Code so strukturiert ist, dass er sicher zu handhaben ist, können Sie gerne mit den Einstellungen fortfahren. Wenn nicht, erwägen Sie, den Bruder von Preferences zu verwenden, Proto-DataStorewie er prophezeit vollständige Sicherheit.
In unserer Anwendung UserPreferencesRepositoryWir geben alle Schlüssel an, die zur Strukturierung der Schlüssel/Wert-Paare unserer persistenten Daten erforderlich sind:
Zum Auslesen gespeicherter Daten, u UserPreferencesRepository wir belichten a Flow<Preferences> von dataStore.data. Dies ermöglicht ein effizientes Vorgehen der letzte erhaltene Zustand ich Sendungen bei jeder Änderung. Mithilfe von Kotlin-Datenklassen können wir alle Sendungen beobachten und den Datenspeicher transformieren Preferences unterliegen unseren UserPreferences Modell, nur verwenden Schlüssel/Wert-Paare wir sind interessiert an:
Flow wird immer entweder einen Wert ausgeben oder verwerfen Sie die Ausnahme beim Versuch, von der Festplatte zu lesen. Wir werden uns in späteren Abschnitten mit der Behandlung von Ausnahmen befassen. Der DataStore stellt außerdem sicher, dass die Arbeit immer erledigt wird Dispatchers.IO Ihr UI-Thread wird also nicht blockiert.
🚨 Erstellen Sie keine Cache-Repositories um den aktuellen Stand Ihrer Präferenzdaten abzubilden. Damit erlischt die DataStore-Garantie für Datenkonsistenz. Wenn Sie eine Aufnahme Ihrer Daten ohne ein Abonnement für weitere Flow-Shows benötigen, verwenden Sie sie stattdessen dataStore.data.first():
Wir werden die Aussetzung verwenden, um Daten zu schreiben DataStore<Preferences>.edit(transform: suspend (MutablePreferences) -> Unit) Funktion. Lassen Sie es uns aufschlüsseln:
DataStore<Preferences> Schnittstelle – wir verwenden derzeit den Datenspeicher als Beton Preferences Implementierung
transform: suspend (MutablePreferences) -> Unit – ein Suspend-Block, der verwendet wird, um diese Änderungen auf unsere persistenten Daten anzuwenden
MutablePreferences – variable Unterklasse Preferencesähnlich MutableMapwodurch wir Änderungen an unseren Schlüssel/Wert-Paaren vornehmen können
Als Beispiel werden wir unsere ändern SHOW_COMPLETED Flagge:
Die Bearbeitung von Daten erfolgt transaktional in einem Atomare Read-Modify-Write-Operation. Das bedeutet, dass es eine bestimmte Abfolge von Datenverarbeitungsvorgängen garantiert, während derer Daten für andere Threads gesperrt sind Konsistenz ich verhindert Rennbedingungen. Nur nach transform ich edit Corutins erfolgreich abgeschlossen, Daten werden dauerhaft auf der Festplatte gespeichert und datastore.data Der Fluss wird die Aktualisierung widerspiegeln.
🚨 Beachten Sie, dass dies die einzige Möglichkeit ist, den Status des DataStore zu ändern. Wartung u MutablePreferences Referenz und manuelle Mutation danach transform vervollständigt ändert die permanenten Daten nicht im DataStore, also sollten Sie nicht versuchen, es zu ändern MutablePreferences außen transform Block.
Wenn der Schreibvorgang aus irgendeinem Grund fehlschlägt, wird die Transaktion beendet und eine Ausnahme ausgelöst.
Wenn Sie es zuvor verwendet haben SharedPreferences in Ihrer Bewerbung und möchten Ihre Daten sicher an Preferences übertragen, können Sie verwenden SharedPreferencesMigration. Dazu braucht es Kontext, SharedPreferences den Namen und den optionalen Schlüsselsatz, den Sie migrieren möchten (oder lassen Sie es einfach so Ursprünglich MIGRATE_ALL_KEYS Wert).
Ansehen SharedPreferencesMigration Implementierung sehen Sie a getMigrationFunction() die dafür verantwortlich ist, alle erforderlichen, gespeicherten Schlüssel/Wert-Paare abzurufen und sie zu den Einstellungen hinzuzufügen gleichen Tasten. Bestehen SharedPreferencesMigration über dem produceMigrations ParameterpreferencesDataStore delegieren an einfach zu migrieren:
produceMigrations wird dafür sorgen getMigrationFunction() wurde gestartet vor einem möglichen Zugriff auf Daten im Datenspeicher. Das bedeutet Ihre Migration ihm muss es gelungen sein bevor der DataStore weitere Werte aussendet und bevor er mit neuen Datenänderungen beginnt. Sobald Sie erfolgreich migriert haben, können Sie die Verwendung sicher einstellen SharedPreferenceswie Schlüssel nur einmal migriert und dann ENTFERNT von SharedPreferences.
Der produceMigrations akzeptiert die Liste DataMigration. Wir werden in späteren Episoden sehen, wie wir dies für andere Arten von Datenmigrationen verwenden können. Wenn Sie nicht migrieren müssen, können Sie dies ignorieren, da es eine gibt Ursprünglich listOf() bereitgestellt schon.
Einer der Hauptvorteile von DataStore gegenüber SharedPreferences ist seine ein geordneter Mechanismus zum Erfassen und Behandeln von Ausnahmen. Während SharedPreferences wirft Parsing-Fehler als Laufzeitausnahmen aus und lässt Raum für unerwartete, nicht abgefangene Abstürze, DataStore wirft aus IOException wenn beim Lesen/Schreiben von Daten ein Fehler auftritt.
Wir können dies sicher lösen, indem wir verwenden catch() Flow-Operator kurz davor map() und Rundfunk emptyPreferences():
Oder mit dem einfachen try-catch Blockschreiben:
Wenn eine andere Art von Ausnahme ausgelöst wird, sollten Sie sie besser erneut auslösen.
Wir deckten ab DataStore-Einstellungen Implementierung – wann und wie man damit Daten liest und schreibt, wie man mit Fehlern umgeht und wie man sie überwindet SharedPreferences. Im nächsten Beitrag werden wir die gleichen Themen mit behandeln Proto-DataStore Umsetzung, also bleiben.
Willkommen bei Now in Android, Ihrem ständigen Leitfaden für Neues und Wichtiges in der Welt der Android-Entwicklung. Es ist lange her seit unserem letzten echten Update; Unser letzter Blog konzentrierte sich auf einige wichtige Momente im Jahr 2021, daher könnte diese Ausgabe besser „Kürzlich in Android“ betitelt werden.
Derselbe Inhalt wie in diesem Beitrag, aber mit weniger Details und unendlich mehr Geschichten.
Wir haben die erste Ausgabe gemacht Jetpack-Blick verfügbar, ein neuer Rahmen, der das Erstellen von App-Widgets für den Startbildschirm und andere Oberflächen schneller und einfacher machen soll. Glance bietet ähnliche moderne, deklarative Kotlin-APIs, die Sie gewohnt sind Jetpack komponieren, was Ihnen hilft, schöne App-Widgets mit viel weniger Code zu erstellen. Glance bietet einen grundlegenden Satz proprietärer Composables, die dabei helfen, „visuelle“ Erfahrungen zu erstellen – beginnend heute mit Anwendungs-Widget-Komponenten, aber mit mehr. Mithilfe der Jetpack Compose-Laufzeit übersetzt Glance diese Composables in RemoteViews, die im App-Widget angezeigt werden können.
Wir fingen an Jetpack Watch Face-Bibliothek von Grund auf in Kotlin geschrieben, einschließlich aller Funktionalitäten aus der Wearable Support Library zusammen mit vielen neuen Features wie:
Gesichtsformung der Uhr, die sowohl auf der Uhr als auch auf dem Telefon hält (ohne dass eine eigene Datenbank oder eine begleitende Anwendung erforderlich ist).
Unterstützung für ein WYSIWYG UI zum Konfigurieren des Zifferblatts auf dem Telefon.
Kleinere, separate Bibliotheken (die nur das enthalten, was Sie benötigen).
Akkuverbesserungen durch die Förderung guter Akkunutzungsmuster außerhalb der Box, z. B. automatische Reduzierung der interaktiven Bildrate, wenn der Akku fast leer ist.
Neue Snapshots-APIs, damit Benutzer Übersichten über Änderungen an ihrer Uhr in Echtzeit sowohl auf der Uhr als auch auf dem Telefon sehen können.
Wenn Sie immer noch die Wearable Support Library verwenden, empfehlen wir Ihnen dringend, zu den neuen Jetpack-Bibliotheken zu wechseln, um die Vorteile der neuen APIs und bevorstehenden Funktionen und Fehlerbehebungen zu nutzen.
Wir begannen eine ein aktualisierter Leitfaden zur Anwendungsarchitektur die Best Practices enthält. Da Android-Apps immer größer werden, ist es wichtig, Code mit einer integrierten Architektur zu entwerfen, die die Skalierung der App ermöglicht, die Qualität und Robustheit verbessert und das Testen erleichtert. Der Leitfaden enthält Seiten für Benutzeroberfläche, Domainich Daten Ebenen, einschließlich eines tiefen Eintauchens in komplexere Themen, wie z. B. den Umgang mit UI-Ereignissen. Wir haben auch eine Lernweg um dich da durch zu bringen.
Das haben wir angekündigt Anwendungen öffnen für Google Play Games auf dem PC als Beta in Korea, Taiwan und Hongkong, sodass Beta-Benutzer den Google Play Games-Katalog auf ihrem PC über eine von Google erstellte eigenständige App spielen können. Der Entwicklerseite hat ein Formular, um Interesse zu bekunden, zusammen mit Informationen zur Erstellung Ihres Android-Spiels auf dem PC. Enthält viele der gleichen Updates, die Sie zur Optimierung Ihres Spiels für Chrome OS-Geräte durchführen, z. B. Unterstützung für Maus- und Tastatursteuerung.
MAD-Fähigkeiten entwickelt sich ständig weiter, mit technischen Inhalten zur modernen Entwicklung von Android.
Erste, Murat behandelte ausführlicher die Konstruktion benutzerdefinierter Plugins, einschließlich der Artifact-API zusätzlich zu der zuvor behandelten Variant-API. Es demonstriert die Konstruktion eines Add-Ons, das den im Anwendungsmanifest angegebenen Versionscode automatisch mit der Git-Version aktualisiert. Mit der Veröffentlichung von AGP 7.0 können Sie diese APIs verwenden, um Eingaben zu steuern, um mittlere und letzte Artefakte zu erstellen, zu lesen, zu ändern oder sogar zu ersetzen.
Nächste, Alex SaveauBetreuer von Gradle Play Publisher und Version Orchestrator, bietet eine Anleitung zur Bearbeitung Ihrer Android-Build-Artefakte mit AGP und Gradle API.
Simona gestartet MAD-Fähigkeiten: DataStore. DataStore ist eine nicht blockierende oder Thread-sichere Bibliothek in Android Jetpack, die eine sichere und konsistente Möglichkeit zum Speichern kleiner Datenmengen wie Einstellungen oder Anwendungsstatus bietet und SharedPreferences ersetzt. Stellt eine Implementierung bereit, die typisierte Objekte speichert, die von Protokollpuffern unterstützt werden (Proto DataStore), und eine Implementierung, die Schlüssel/Wert-Paare speichert (Preferences DataStore).
Aber warte! Als ob das nicht genug wäre, gibt es noch mehr VERRÜCKTE Inhalte!
Sie können alle Hinweise zum AndroidX-Release sehen Hier.
Alexschrieb über die letzten Updates für Jetnews was sein Verhalten auf großen und kleinen Mobilgeräten verbessert. Es beschreibt unseren Design- und Entwicklungsprozess, damit Sie unsere Philosophie und die zugehörigen Implementierungsschritte kennenlernen können, um eine Anwendung zu erstellen, die für alle Jetpack Compose-Bildschirme optimiert ist, einschließlich der Planung einer Liste / eines Details.
Paulschrieb über Drag & Drop und wie man Android Jetpack macht DragAndDrop-Alpha-Bibliothek erleichtert die Handhabung von in Ihre Anwendung eingefügten Daten.
Der Reihe Barrierefreiheit Es geht weiter und weiter, beginnend mit einer Episode über die richtige Implementierung von UI-Elementen, die nach einer bestimmten Zeit verschwinden.
Wir behandeln auch wie Scanner für Barrierefreiheit kann Ihnen dabei helfen, Ihre App für alle Benutzer zu verbessern, indem Verbesserungen der Barrierefreiheit vorgeschlagen werden.
Mayuri abgedeckte Best Practices für Siehe Nächste API auf Android TV und Google TV, wodurch die Interaktion mit Ihrer App gesteigert wird, indem Ihre Inhalte in der Warteschlange „Als Nächstes ansehen“ angezeigt werden.
Es gab drei Episoden Android-Entwickler hinter den Kulissen seit dem letzten Now in Android veröffentlicht. Überprüfen Sie sie unter dem folgenden Link oder in Ihrem bevorzugten Podcast-Client: