Rozwiązywanie typowych problemów międzyplatformowych podczas pracy z Flutter
Opublikowany: 2022-03-10Widziałem wiele zamieszania w sieci, jeśli chodzi o tworzenie stron internetowych za pomocą Fluttera i często jest to niestety z niewłaściwych powodów.
W szczególności ludzie czasami mylą go ze starszymi, opartymi na sieci Web, międzyplatformowymi platformami mobilnymi (i stacjonarnymi), które w zasadzie były po prostu stronami internetowymi uruchomionymi w przeglądarkach uruchomionych w aplikacji owijającej.
To było naprawdę wieloplatformowe w tym sensie, że interfejsy i tak były takie same, ponieważ miałeś dostęp tylko do interfejsów normalnie dostępnych w Internecie.
Flutter nie jest jednak taki: działa natywnie na każdej platformie i oznacza to, że każda aplikacja działa tak, jak gdyby była napisana w Javie/Kotlin lub Objective-C/Swift na Androidzie i iOS. Musisz to wiedzieć, ponieważ oznacza to, że musisz zadbać o wiele różnic między tymi bardzo zróżnicowanymi platformami.
W tym artykule przyjrzymy się niektórym z tych różnic i sposobom ich przezwyciężenia. Dokładniej, porozmawiamy o różnicach w pamięci i interfejsie użytkownika, które najczęściej powodują zamieszanie wśród programistów podczas pisania kodu Fluttera, który ma być wieloplatformowy.
Przykład 1: Przechowywanie
Niedawno pisałem na swoim blogu o potrzebie innego podejścia do przechowywania tokenów JWT w aplikacjach internetowych w porównaniu z aplikacjami mobilnymi.
Wynika to z odmiennego charakteru opcji przechowywania platform i konieczności znajomości każdego z ich natywnych narzędzi programistycznych.
Sieć
Gdy piszesz aplikację internetową, masz następujące opcje przechowywania:
- pobieranie/przesyłanie plików na/z dysku, co wymaga interakcji użytkownika i dlatego jest odpowiednie tylko dla plików przeznaczonych do odczytu lub tworzenia przez użytkownika;
- używanie plików cookie, które mogą, ale nie muszą być dostępne z JS (w zależności od tego, czy są to
httpOnly
) i są automatycznie wysyłane wraz z żądaniami do danej domeny i zapisywane, gdy przychodzą jako część odpowiedzi; - za pomocą JS
localStorage
isessionStorage
, dostępnych przez dowolny JS w witrynie, ale tylko z JS, który jest częścią stron tej witryny.
mobilny
Zupełnie inaczej wygląda sytuacja z aplikacjami mobilnymi. Opcje przechowywania są następujące:
- lokalne dokumenty aplikacji lub pamięć podręczna, do których ta aplikacja ma dostęp;
- inne lokalne ścieżki przechowywania plików utworzonych/odczytywalnych przez użytkownika;
-
NSUserDefaults
iSharedPreferences
odpowiednio w systemach iOS i Android do przechowywania danych typu klucz-wartość; -
Keychain
na iOS iKeyStore
na Androidzie do bezpiecznego przechowywania, odpowiednio, dowolnych danych i kluczy kryptograficznych.
Jeśli tego nie wiesz, narobisz bałaganu w swoich implementacjach, ponieważ musisz wiedzieć, jakiego rozwiązania pamięci masowej używasz i jakie są zalety i wady.
Rozwiązania wieloplatformowe: podejście wstępne
Korzystanie z pakietu shared_preferences
Flutter wykorzystuje localStorage
w sieci Web, SharedPreferences
w systemie Android i NSUserDefaults
w systemie iOS. Mają one zupełnie inne konsekwencje dla Twojej aplikacji, zwłaszcza jeśli przechowujesz poufne informacje, takie jak tokeny sesji: localStorage
może zostać odczytany przez klienta, więc jest to problem, jeśli jesteś podatny na XSS. Mimo że aplikacje mobilne nie są tak naprawdę podatne na XSS, SharedPreferences
i NSUserDefaults
nie są bezpiecznymi metodami przechowywania, ponieważ mogą zostać naruszone po stronie klienta, ponieważ nie są bezpieczne i nie są szyfrowane. Dzieje się tak, ponieważ są one przeznaczone do preferencji użytkownika, jak wspomniano tutaj w przypadku iOS i tutaj w dokumentacji Androida, gdy mówimy o bibliotece Security, która została zaprojektowana w celu zapewnienia otoki dla SharedPreferences
specjalnie w celu zaszyfrowania danych przed ich przechowywaniem.
Bezpieczne przechowywanie na urządzeniach mobilnych
Jedynymi bezpiecznymi rozwiązaniami do przechowywania na urządzeniach mobilnych są Keychain
i KeyStore
odpowiednio w systemach iOS i Android, podczas gdy w Internecie nie ma bezpiecznego przechowywania .
Keychain
i KeyStore
mają jednak bardzo różny charakter: pęk Keychain
to ogólne rozwiązanie do przechowywania danych uwierzytelniających, podczas gdy KeyStore
służy do przechowywania (i może generować) klucze kryptograficzne , albo klucze symetryczne, albo klucze publiczne/prywatne.
Oznacza to, że jeśli na przykład musisz przechowywać token sesji, w systemie iOS możesz pozwolić systemowi operacyjnemu zarządzać częścią szyfrowania i po prostu wysłać token do Keychain
, podczas gdy w systemie Android jest to trochę bardziej ręczne, ponieważ potrzebujesz aby wygenerować (nie na stałe, to źle) klucz, użyj go do zaszyfrowania tokena, przechowuj zaszyfrowany token w SharedPreferences
i przechowuj klucz w KeyStore
.
Istnieją różne podejścia do tego, podobnie jak większość rzeczy związanych z bezpieczeństwem, ale najprostszym jest prawdopodobnie użycie szyfrowania symetrycznego, ponieważ nie ma potrzeby kryptografii klucza publicznego, ponieważ Twoja aplikacja zarówno szyfruje, jak i odszyfrowuje token.
Oczywiście nie musisz pisać kodu specyficznego dla platformy mobilnej, który robi to wszystko, ponieważ istnieje na przykład wtyczka Flutter, która to wszystko robi.
Brak bezpiecznego przechowywania w sieci
To był właściwie powód, który zmusił mnie do napisania tego posta. Pisałem o używaniu tego pakietu do przechowywania JWT w aplikacjach mobilnych i ludzie chcieli mieć wersję internetową , ale, jak powiedziałem, nie ma bezpiecznego przechowywania w sieci . Nie istnieje.
Czy to oznacza, że twój JWT musi być otwarty?
Nie, wcale. Możesz używać httpOnly
plików cookie, prawda? Nie są one dostępne dla JS i są wysyłane tylko na Twój serwer. Problem polega na tym, że są one zawsze wysyłane na Twój serwer, nawet jeśli jeden z Twoich użytkowników kliknie adres URL żądania GET w witrynie innej osoby, a to żądanie GET ma skutki uboczne, które nie spodobają się Tobie lub Twojemu użytkownikowi. Działa to również w przypadku innych typów żądań, jest to po prostu bardziej skomplikowane. Nazywa się Cross-Site Request Forgery i nie chcesz tego. Jest to jedno z zagrożeń bezpieczeństwa sieci wspomnianych w dokumentacji MDN Mozilli, gdzie można znaleźć pełniejsze wyjaśnienie.
Istnieją metody zapobiegania. Najpopularniejszym z nich jest posiadanie dwóch tokenów: jeden z nich trafia do klienta jako plik cookie httpOnly
, a drugi jako część odpowiedzi. Te ostatnie muszą być przechowywane w localStorage
, a nie w plikach cookie, ponieważ nie chcemy, aby były one automatycznie wysyłane na serwer.
Rozwiązywanie obu
Co zrobić, jeśli masz zarówno aplikację mobilną, jak i aplikację internetową?
Można sobie z tym poradzić na dwa sposoby:
- Użyj tego samego punktu końcowego zaplecza, ale ręcznie pobieraj i wysyłaj pliki cookie za pomocą nagłówków HTTP związanych z plikami cookie;
- Utwórz oddzielny punkt końcowy zaplecza inny niż sieci Web, który generuje inny token niż którykolwiek token używany przez aplikację sieci Web, a następnie zezwól na zwykłą autoryzację JWT, jeśli klient jest w stanie udostępnić token tylko dla urządzeń mobilnych.
Uruchamianie innego kodu na różnych platformach
Zobaczmy teraz, jak możemy uruchomić inny kod na różnych platformach, aby móc skompensować różnice.
Tworzenie wtyczki Flutter
Szczególnie, aby rozwiązać problem przechowywania, jednym ze sposobów, w jaki możesz to zrobić, jest pakiet wtyczek: wtyczki zapewniają wspólny interfejs Dart i mogą uruchamiać inny kod na różnych platformach, w tym natywny kod Kotlin/Java lub Swift/Objective-C . Tworzenie pakietów i wtyczek jest dość skomplikowane, ale jest wyjaśnione w wielu miejscach w sieci i innych miejscach (na przykład w książkach Fluttera), w tym w oficjalnej dokumentacji Fluttera.
Na przykład w przypadku platform mobilnych istnieje już wtyczka do bezpiecznego przechowywania, a jest to flutter_secure_storage
, której przykład użycia można znaleźć tutaj, ale która nie działa na przykład w sieci.
Z drugiej strony, w przypadku prostego przechowywania kluczy i wartości, które działa również w sieci, istnieje wieloplatformowy pakiet wtyczek opracowany przez Google o nazwie shared_preferences
, który zawiera komponent specyficzny dla sieci Web o nazwie shared_preferences_web
, który używa NSUserDefaults
, SharedPreferences
lub localStorage
w zależności od platformy.
TargetPlatform na Flutterze
Po zaimportowaniu package:flutter/foundation.dart
, możesz porównać Theme.of(context).platform
z wartościami:
-
TargetPlatform.android
-
TargetPlatform.iOS
-
TargetPlatform.linux
-
TargetPlatform.windows
-
TargetPlatform.macOS
-
TargetPlatform.fuchsia
i napisz swoje funkcje tak, aby dla każdej platformy, którą chcesz obsługiwać, robiły odpowiednią rzecz. Będzie to szczególnie przydatne w następnym przykładzie różnicy platform, czyli różnic w sposobie wyświetlania widżetów na różnych platformach.
W szczególności w tym przypadku istnieje również dość popularna wtyczka flutter_platform_widgets
, która upraszcza tworzenie widżetów uwzględniających platformę.
Przykład 2: Różnice w sposobie wyświetlania tego samego widżetu
Nie możesz po prostu napisać kodu wieloplatformowego i udawać, że przeglądarka, telefon, komputer i smartwatch to to samo — chyba że chcesz, aby Twoja aplikacja na Androida i iOS była WebView, a aplikacja na komputery była zbudowana za pomocą Electrona . Jest wiele powodów, aby tego nie robić, i nie jest celem tego artykułu, aby przekonać Cię do korzystania z frameworków takich jak Flutter, które utrzymują Twoją aplikację natywną, ze wszystkimi korzyściami związanymi z wydajnością i wrażeniami użytkownika, jednocześnie pozwalając na napisz kod, który przez większość czasu będzie taki sam dla wszystkich platform.
Wymaga to jednak troski i uwagi, a także przynajmniej podstawowej wiedzy na temat platform, które chcesz obsługiwać, ich rzeczywistych natywnych interfejsów API i tego wszystkiego. Użytkownicy React Native muszą zwracać na to jeszcze większą uwagę, ponieważ platforma korzysta z wbudowanych widżetów systemu operacyjnego, więc tak naprawdę musisz zwracać jeszcze większą uwagę na wygląd aplikacji, testując ją intensywnie na obu platformach, bez możliwości przełączania się między Widżet iOS i materiałów w locie, tak jak to możliwe w przypadku Fluttera.
Co zmienia się bez Twojej prośby
Niektóre aspekty interfejsu użytkownika aplikacji są automatycznie zmieniane po zmianie platformy. W tej sekcji wspomniano również o zmianach między Flutterem a React Native w tym zakresie.
Między Androidem a iOS (Flutter)
Flutter jest w stanie renderować widżety materiałowe na iOS (i widżety Cupertino (podobne do iOS) na Androida), ale to, czego NIE robi, to pokazywać dokładnie to samo na Androidzie i iOS: Tematyka materiałów szczególnie dostosowuje się do konwencji każdej platformy .
Na przykład animacje nawigacji i przejścia oraz czcionki domyślne są inne, ale nie mają one tak dużego wpływu na aplikację.
To, co może wpłynąć na niektóre z Twoich wyborów, jeśli chodzi o estetykę lub UX, to fakt, że niektóre elementy statyczne również się zmieniają. W szczególności niektóre ikony zmieniają się między dwiema platformami, tytuły paska aplikacji znajdują się pośrodku w systemie iOS i po lewej stronie w systemie Android (po lewej stronie dostępnego miejsca w przypadku przycisku Wstecz lub przycisku otwierania szuflady (wyjaśnione tutaj w wytycznych Material Design i znany również jako menu hamburgerów).Oto jak wygląda aplikacja Material z szufladą na Androida:
I jak wygląda ta sama, bardzo prosta aplikacja Material na iOS:
Między urządzeniami mobilnymi i internetowymi oraz z wycięciami na ekranie (trzepotanie)
W sieci jest trochę inna sytuacja, o czym wspomniano również w tym artykule Smashing o responsywnym tworzeniu stron internetowych za pomocą Fluttera: w szczególności oprócz konieczności optymalizacji pod kątem większych ekranów i uwzględnienia sposobu, w jaki ludzie spodziewają się nawigować po Twojej witrynie — na czym skupia się ten artykuł — musisz się martwić, że czasami widżety są umieszczane poza oknem przeglądarki. Ponadto niektóre telefony mają wycięcia w górnej części ekranu lub inne przeszkody w prawidłowym przeglądaniu aplikacji z powodu jakiejś przeszkody.
Obu tych problemów można uniknąć, zawijając widżety w widżet SafeArea
, który jest szczególnym rodzajem widżetu wypełniającego, który zapewnia, że widżety trafiają do miejsca, w którym mogą być faktycznie wyświetlane, nie utrudniając użytkownikom ich zobaczenia, czy to ograniczenie sprzętowe, czy programowe.
W React Native
React Native wymaga znacznie większej uwagi i znacznie głębszej wiedzy na temat każdej platformy, a ponadto wymaga uruchomienia symulatora iOS i emulatora Androida przynajmniej po to, aby móc przetestować swoją aplikację na obu platformach: tak nie jest to samo i konwertuje elementy interfejsu użytkownika JavaScript na widżety specyficzne dla platformy. Innymi słowy, Twoje aplikacje React Native zawsze będą wyglądać jak iOS — z elementami interfejsu użytkownika Cupertino, jak się je czasami nazywa — a aplikacje na Androida zawsze będą wyglądać jak zwykłe aplikacje Material Design na Androida, ponieważ korzystają z widżetów platformy.
Różnica polega na tym, że Flutter renderuje swoje widżety za pomocą własnego silnika renderowania niskiego poziomu, co oznacza, że możesz testować obie wersje aplikacji na jednej platformie.
Obejście tego problemu
Jeśli nie szukasz czegoś bardzo konkretnego, Twoja aplikacja powinna wyglądać inaczej na różnych platformach, w przeciwnym razie niektórzy użytkownicy będą niezadowoleni.
Tak jak nie powinieneś po prostu wysyłać aplikacji mobilnej do sieci (jak pisałem we wspomnianym poście Smashing), nie powinieneś na przykład wysyłać aplikacji pełnej widżetów z Cupertino użytkownikom Androida, ponieważ będzie to mylące dla Największa część. Z drugiej strony, możliwość faktycznego uruchomienia aplikacji, która ma widżety przeznaczone dla innej platformy, pozwala przetestować aplikację i pokazać ją osobom w obu wersjach bez konieczności używania do tego dwóch urządzeń.
Druga strona: używanie niewłaściwych widżetów z właściwych powodów
Ale oznacza to również, że większość prac programistycznych Fluttera możesz wykonać na stacji roboczej z systemem Linux lub Windows bez poświęcania doświadczenia użytkowników iOS, a następnie po prostu zbudować aplikację na inną platformę i nie martwić się o jej dokładne testowanie.
Następne kroki
Platformy międzyplatformowe są niesamowite, ale przenoszą odpowiedzialność na Ciebie, programistę, aby zrozumieć, jak działa każda platforma i jak upewnić się, że Twoja aplikacja dostosowuje się i jest przyjemna w użyciu dla Twoich użytkowników. Inne drobiazgi do rozważenia to, na przykład, użycie różnych opisów tego, co może być w istocie tym samym, jeśli istnieją różne konwencje na różnych platformach.
Wspaniale jest nie musieć tworzyć dwóch (lub więcej) aplikacji osobno przy użyciu różnych języków, ale nadal musisz pamiętać, że w istocie tworzysz więcej niż jedną aplikację, a to wymaga myślenia o każdej z tworzonych aplikacji .
Dalsze zasoby
- Witryna Flutter Gallery i aplikacja na Androida, prezentująca użycie widżetów Flutter typowych dla różnych platform i ich agnostycyzmu od platformy
- Dokumentacja Flutter API na TargetPlatform
- Dokumentacja Fluttera dotycząca tworzenia pakietów i wtyczek
- Dokumentacja Flutter o adaptacjach platformy
- Dokumentacja MDN dotycząca plików cookie