Lösung häufiger plattformübergreifender Probleme bei der Arbeit mit Flutter

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ Bei der Verwendung von plattformübergreifenden Frameworks vergessen die Leute möglicherweise die Nuancen der einzelnen Plattformen, auf denen ihr Code ausgeführt werden soll. Dieser Artikel soll darauf eingehen.

Ich habe online viel Verwirrung in Bezug auf die Webentwicklung mit Flutter gesehen, und leider oft aus den falschen Gründen.

Insbesondere wird es manchmal mit den älteren webbasierten plattformübergreifenden Frameworks für Mobilgeräte (und Desktops) verwechselt, die im Grunde nur Webseiten waren, die in Browsern ausgeführt wurden, die in einer Wrapper-App ausgeführt wurden.

Das war wirklich plattformübergreifend in dem Sinne, dass die Schnittstellen sowieso gleich waren, weil Sie nur Zugriff auf die Schnittstellen hatten, die normalerweise im Web zugänglich sind.

Flutter ist das jedoch nicht: Es läuft nativ auf jeder Plattform, und es bedeutet, dass jede App genauso läuft, wie sie laufen würde, wenn sie in Java/Kotlin oder Objective-C/Swift auf Android und iOS geschrieben wäre, ziemlich genau. Sie müssen das wissen, denn dies bedeutet, dass Sie sich um die vielen Unterschiede zwischen diesen sehr unterschiedlichen Plattformen kümmern müssen.

In diesem Artikel werden wir einige dieser Unterschiede sehen und wie man sie überwindet. Genauer gesagt werden wir über Speicher- und UI-Unterschiede sprechen, die bei Entwicklern am häufigsten Verwirrung stiften, wenn sie Flutter-Code schreiben, der plattformübergreifend sein soll.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Beispiel 1: Lagerung

Ich habe kürzlich in meinem Blog über die Notwendigkeit eines anderen Ansatzes zum Speichern von JWTs in Web-Apps im Vergleich zu mobilen Apps geschrieben.

Das liegt an der unterschiedlichen Art der Speicheroptionen der Plattformen und der Notwendigkeit, jede und ihre nativen Entwicklungstools zu kennen.

Netz

Wenn Sie eine Web-App schreiben, haben Sie folgende Speicheroptionen:

  1. Herunterladen/Hochladen von Dateien auf/von der Festplatte, was eine Benutzerinteraktion erfordert und daher nur für Dateien geeignet ist, die vom Benutzer gelesen oder erstellt werden sollen;
  2. Verwendung von Cookies, auf die von JS zugegriffen werden kann oder nicht (je nachdem, ob sie httpOnly sind oder nicht) und automatisch zusammen mit Anfragen an eine bestimmte Domain gesendet und gespeichert werden, wenn sie als Teil einer Antwort kommen;
  3. Verwendung von JS localStorage und sessionStorage , auf die von jedem JS auf der Website zugegriffen werden kann, jedoch nur von JS, das Teil der Seiten dieser Website ist.

Handy, Mobiltelefon

Ganz anders sieht es bei mobilen Apps aus. Die Speicheroptionen sind die folgenden:

  1. lokale App-Dokumente oder Cache-Speicher, auf die diese App zugreifen kann;
  2. andere lokale Speicherpfade für vom Benutzer erstellte/lesbare Dateien;
  3. NSUserDefaults bzw. SharedPreferences auf iOS und Android für die Speicherung von Schlüsselwerten;
  4. Keychain auf iOS und KeyStore auf Android zur sicheren Aufbewahrung beliebiger Daten bzw. kryptografischer Schlüssel.

Wenn Sie das nicht wissen, werden Sie Ihre Implementierungen durcheinander bringen, weil Sie wissen müssen, welche Speicherlösung Sie tatsächlich verwenden und welche Vor- und Nachteile sie hat.

Plattformübergreifende Lösungen: Ein erster Ansatz

Die Verwendung des Pakets Flutter shared_preferences verwendet localStorage im Web, SharedPreferences auf Android und NSUserDefaults auf iOS. Diese haben völlig andere Auswirkungen auf Ihre App, insbesondere wenn Sie vertrauliche Informationen wie Sitzungstoken speichern: localStorage kann vom Client gelesen werden, daher ist dies ein Problem, wenn Sie anfällig für XSS sind. Obwohl mobile Apps nicht wirklich anfällig für XSS sind, sind SharedPreferences und NSUserDefaults keine sicheren Speichermethoden, da sie auf der Clientseite kompromittiert werden können, da sie kein sicherer Speicher und nicht verschlüsselt sind. Das liegt daran, dass sie für Benutzereinstellungen gedacht sind, wie hier im Fall von iOS und hier in der Android-Dokumentation erwähnt, wenn es um die Sicherheitsbibliothek geht, die entwickelt wurde, um Wrapper für die SharedPreferences , um die Daten vor dem Speichern zu verschlüsseln.

Sicherer Speicher auf Mobilgeräten

Die einzigen sicheren Speicherlösungen für Mobilgeräte sind Keychain und KeyStore für iOS bzw. Android, während es im Web keinen sicheren Speicher gibt .

Der Keychain und KeyStore sind jedoch sehr unterschiedlich: Keychain ist eine generische Speicherlösung für Anmeldeinformationen, während der KeyStore zum Speichern (und Generieren) kryptografischer Schlüssel verwendet wird, entweder symmetrische Schlüssel oder öffentliche/private Schlüssel.

Das bedeutet, dass Sie, wenn Sie beispielsweise ein Sitzungstoken speichern müssen, unter iOS das Betriebssystem den Verschlüsselungsteil verwalten lassen und Ihr Token einfach an den Keychain senden können, während es unter Android eher eine manuelle Erfahrung ist, weil Sie es brauchen Um einen Schlüssel zu generieren (nicht fest codiert, das ist schlecht), verwenden Sie ihn zum Verschlüsseln des Tokens, speichern Sie das verschlüsselte Token in SharedPreferences und speichern Sie den Schlüssel im KeyStore .

Wie bei den meisten Dingen in der Sicherheit gibt es dafür verschiedene Ansätze, aber der einfachste ist wahrscheinlich die symmetrische Verschlüsselung, da keine Kryptografie mit öffentlichen Schlüsseln erforderlich ist, da Ihre App das Token sowohl verschlüsselt als auch entschlüsselt.

Offensichtlich müssen Sie keinen mobilplattformspezifischen Code schreiben, der all dies tut, da es zum Beispiel ein Flutter-Plugin gibt, das all das tut.

Der Mangel an sicherer Speicherung im Web

Das war eigentlich der Grund, der mich dazu veranlasst hat, diesen Beitrag zu schreiben. Ich habe über die Verwendung dieses Pakets geschrieben, um JWT in mobilen Apps zu speichern, und die Leute wollten die Webversion davon, aber wie gesagt, es gibt keinen sicheren Speicher im Web . Es existiert nicht.

Bedeutet das, dass Ihr JWT im Freien sein muss?

Nein überhaupt nicht. Sie können httpOnly Cookies verwenden, nicht wahr? Diese sind für JS nicht zugänglich und werden nur an Ihren Server gesendet. Das Problem dabei ist, dass sie immer an Ihren Server gesendet werden, selbst wenn einer Ihrer Benutzer auf eine GET-Anforderungs-URL auf der Website einer anderen Person klickt und diese GET-Anforderung Nebenwirkungen hat, die Ihnen oder Ihrem Benutzer nicht gefallen werden. Dies funktioniert tatsächlich auch für andere Anfragetypen, es ist nur komplizierter. Es heißt Cross-Site Request Forgery und das wollen Sie nicht. Es gehört zu den Internet-Sicherheitsbedrohungen, die in Mozillas MDN-Dokumentation erwähnt werden, wo Sie eine vollständigere Erklärung finden können.

Es gibt Präventionsmethoden. Am gebräuchlichsten sind tatsächlich zwei Token: Einer davon gelangt als httpOnly -Cookie zum Client, der andere als Teil der Antwort. Letzteres muss in localStorage und nicht in Cookies gespeichert werden, da wir nicht möchten, dass es automatisch an den Server gesendet wird.

Beides lösen

Was ist, wenn Sie sowohl eine mobile App als auch eine Web-App haben?

Dem kann auf zwei Arten begegnet werden:

  1. Verwenden Sie denselben Back-End-Endpunkt, aber rufen Sie die Cookies manuell ab und senden Sie sie mithilfe der cookiebezogenen HTTP-Header.
  2. Erstellen Sie einen separaten Nicht-Web-Back-End-Endpunkt, der ein anderes Token als jedes von der Web-App verwendete Token generiert, und erlauben Sie dann eine reguläre JWT-Autorisierung, wenn der Client das Token nur für Mobilgeräte bereitstellen kann.

Ausführen von unterschiedlichem Code auf unterschiedlichen Plattformen

Sehen wir uns nun an, wie wir unterschiedlichen Code auf verschiedenen Plattformen ausführen können, um die Unterschiede ausgleichen zu können.

Erstellen eines Flutter-Plugins

Insbesondere um das Speicherproblem zu lösen, können Sie dies mit einem Plugin-Paket tun: Plugins bieten eine gemeinsame Dart-Schnittstelle und können auf verschiedenen Plattformen unterschiedlichen Code ausführen, einschließlich nativem plattformspezifischem Kotlin/Java- oder Swift/Objective-C-Code . Das Entwickeln von Paketen und Plugins ist ziemlich komplex, wird aber an vielen Stellen im Web und anderswo (z. B. in Flutter-Büchern) erklärt, einschließlich der offiziellen Flutter-Dokumentation.

Für mobile Plattformen gibt es beispielsweise bereits ein Secure-Storage-Plugin, und das ist flutter_secure_storage , für das Sie hier ein Anwendungsbeispiel finden, aber das funktioniert beispielsweise nicht im Web.

Andererseits gibt es für eine einfache Schlüsselwertspeicherung, die auch im Web funktioniert, ein plattformübergreifendes, von Google entwickeltes Erstanbieter-Plug-in-Paket namens shared_preferences , das eine webspezifische Komponente namens shared_preferences_web enthält, die NSUserDefaults , SharedPreferences oder localStorage abhängig von der Plattform.

TargetPlatform auf Flutter

Nach dem Import von package:flutter/foundation.dart können Sie Theme.of(context).platform mit den Werten vergleichen:

  • TargetPlatform.android
  • TargetPlatform.iOS
  • TargetPlatform.linux
  • TargetPlatform.windows
  • TargetPlatform.macOS
  • TargetPlatform.fuchsia

und schreiben Sie Ihre Funktionen so, dass sie für jede Plattform, die Sie unterstützen möchten, das Passende tun. Dies ist besonders nützlich für das nächste Beispiel für Plattformunterschiede, nämlich Unterschiede in der Anzeige von Widgets auf verschiedenen Plattformen.

Speziell für diesen Anwendungsfall gibt es auch ein einigermaßen beliebtes flutter_platform_widgets Plugin, das die Entwicklung plattformfähiger Widgets vereinfacht.

Beispiel 2: Unterschiede in der Darstellung desselben Widgets

Sie können nicht einfach plattformübergreifenden Code schreiben und so tun, als wären ein Browser, ein Telefon, ein Computer und eine Smartwatch dasselbe – es sei denn, Sie möchten, dass Ihre Android- und iOS-App ein WebView ist und Ihre Desktop-App mit Electron erstellt wird . Es gibt viele Gründe, dies nicht zu tun, und es ist nicht der Sinn dieses Artikels, Sie davon zu überzeugen, stattdessen Frameworks wie Flutter zu verwenden, die Ihre App nativ halten, mit all den damit verbundenen Leistungs- und Benutzererfahrungsvorteilen, während Sie dies ermöglichen Schreiben Sie Code, der meistens für alle Plattformen gleich ist.

Das erfordert jedoch Sorgfalt und Aufmerksamkeit und zumindest ein grundlegendes Wissen über die Plattformen, die Sie unterstützen möchten, ihre tatsächlichen nativen APIs und all das. React Native-Benutzer müssen dem noch mehr Aufmerksamkeit schenken, da dieses Framework die integrierten Betriebssystem-Widgets verwendet. Sie müssen also noch mehr darauf achten, wie die App aussieht, indem Sie sie ausgiebig auf beiden Plattformen testen, ohne zwischen ihnen wechseln zu können iOS- und Material-Widget im Handumdrehen, wie es mit Flutter möglich ist.

Was ändert sich ohne Ihre Anfrage

Es gibt einige Aspekte der Benutzeroberfläche Ihrer App, die automatisch geändert werden, wenn Sie die Plattform wechseln. In diesem Abschnitt wird auch erwähnt, was sich in dieser Hinsicht zwischen Flutter und React Native ändert.

Zwischen Android und iOS (Flutter)

Flutter ist in der Lage, Material-Widgets auf iOS (und Cupertino (iOS-ähnliche) Widgets auf Android) zu rendern, aber was es NICHT tut, ist, genau dasselbe auf Android und iOS zu zeigen: Material-Themen passen sich speziell an die Konventionen jeder Plattform an .

Zum Beispiel sind Navigationsanimationen und -übergänge und Standardschriftarten unterschiedlich, aber diese wirken sich nicht so sehr auf Ihre App aus.

Was einige Ihrer Entscheidungen in Bezug auf Ästhetik oder UX beeinflussen kann, ist die Tatsache, dass sich auch einige statische Elemente ändern. Insbesondere ändern sich einige Symbole zwischen den beiden Plattformen, App-Leistentitel befinden sich in der Mitte bei iOS und links bei Android (links neben dem verfügbaren Platz, falls es eine Zurück-Schaltfläche oder die Schaltfläche zum Öffnen einer Schublade gibt (hier erklärt). in den Material Design-Richtlinien und auch als Hamburger-Menü bekannt) So sieht eine Material-App mit Drawer auf Android aus:

Bild einer Android-App, das zeigt, wo der Titel der App-Leiste in Flutter-Android-Material-Apps angezeigt wird
Material-App auf Android: Der AppBar-Titel befindet sich auf der linken Seite des verfügbaren Platzes. (Große Vorschau)

Und wie die gleiche, sehr einfache Material-App auf iOS aussieht:

Bild einer iOS-App, das zeigt, wo der Titel der App-Leiste in Flutter-iOS-Material-Apps angezeigt wird
Material-App auf iOS: Der AppBar-Titel befindet sich in der Mitte. (Große Vorschau)

Zwischen Handy und Web und mit Bildschirmkerben (Flutter)

Im Web ist die Situation etwas anders, wie auch in diesem Smashing-Artikel über Responsive Web Development mit Flutter erwähnt: Insbesondere müssen Sie nicht nur für größere Bildschirme optimieren, sondern auch berücksichtigen, wie die Leute erwarten, durch Ihre Website zu navigieren – was der Hauptfokus dieses Artikels ist – müssen Sie sich Sorgen darüber machen, dass Widgets manchmal außerhalb des Browserfensters platziert werden. Außerdem haben einige Telefone Kerben im oberen Teil ihres Bildschirms oder andere Hindernisse für die korrekte Anzeige Ihrer App aufgrund einer Art von Behinderung.

Diese beiden Probleme können vermieden werden, indem Sie Ihre Widgets in ein SafeArea Widget einpacken, das eine besondere Art von Polster-Widget ist, das sicherstellt, dass Ihre Widgets an einem Ort platziert werden, an dem sie tatsächlich angezeigt werden können, ohne dass die Benutzer daran gehindert werden, sie zu sehen. sei es eine Hardware- oder Softwarebeschränkung.

In React Native

React Native erfordert viel mehr Aufmerksamkeit und ein viel tieferes Wissen über jede Plattform, zusätzlich dazu, dass Sie zumindest den iOS-Simulator und den Android-Emulator ausführen müssen, um Ihre App auf beiden Plattformen testen zu können: Das ist es nicht das gleiche und konvertiert seine JavaScript-UI-Elemente in plattformspezifische Widgets. Mit anderen Worten, Ihre React Native-Apps sehen immer wie iOS aus – mit Cupertino-UI-Elementen, wie sie manchmal genannt werden – und Ihre Android-Apps sehen immer wie normale Material Design-Android-Apps aus, weil sie die Widgets der Plattform verwenden.

Der Unterschied besteht hier darin, dass Flutter seine Widgets mit einer eigenen Low-Level-Rendering-Engine rendert, sodass Sie beide App-Versionen auf einer Plattform testen können.

Dieses Problem umgehen

Sofern Sie nicht auf etwas ganz Bestimmtes aus sind, sollte Ihre App auf verschiedenen Plattformen anders aussehen, sonst werden einige Ihrer Benutzer unzufrieden sein.

Genauso wie Sie eine mobile App nicht einfach ins Web schicken sollten (wie ich in dem oben erwähnten Smashing-Beitrag geschrieben habe), sollten Sie beispielsweise keine App voller Cupertino-Widgets an Android-Benutzer schicken, weil es verwirrend sein wird der größte Teil. Andererseits ermöglicht Ihnen die Möglichkeit, eine App mit Widgets auszuführen, die für eine andere Plattform gedacht sind, die App zu testen und sie den Leuten in beiden Versionen zu zeigen, ohne dafür unbedingt zwei Geräte verwenden zu müssen.

Die andere Seite: Die falschen Widgets aus den richtigen Gründen verwenden

Das bedeutet aber auch, dass Sie den größten Teil Ihrer Flutter-Entwicklung auf einer Linux- oder Windows-Workstation durchführen können, ohne die Erfahrung Ihrer iOS-Benutzer zu opfern, und dann einfach die App für die andere Plattform erstellen und sich nicht darum kümmern müssen, sie gründlich zu testen.

Nächste Schritte

Plattformübergreifende Frameworks sind großartig, aber sie übertragen die Verantwortung auf Sie, den Entwickler, um zu verstehen, wie jede Plattform funktioniert und wie Sie sicherstellen können, dass sich Ihre App anpasst und für Ihre Benutzer angenehm zu verwenden ist. Andere kleine Dinge, die zu berücksichtigen sind, können beispielsweise die Verwendung unterschiedlicher Beschreibungen für etwas sein, das im Wesentlichen dasselbe sein könnte, wenn es auf verschiedenen Plattformen unterschiedliche Konventionen gibt.

Es ist großartig, die zwei (oder mehr) Apps nicht separat in verschiedenen Sprachen erstellen zu müssen, aber Sie müssen immer noch bedenken, dass Sie im Wesentlichen mehr als eine App erstellen, und das erfordert, dass Sie über jede der Apps nachdenken, die Sie erstellen .

Weitere Ressourcen

  • Die Flutter Gallery-Website und die Android-App, die die Verwendung von Flutter-Widgets zeigen, die für verschiedene Plattformen typisch sind, und deren Plattformagnostizismus
  • Flutter-API-Dokumentation auf TargetPlatform
  • Flutter-Dokumentation zur Erstellung von Paketen und Plugins
  • Flutter-Dokumentation zu Plattformanpassungen
  • MDN-Dokumentation zu Cookies