Rezolvarea problemelor comune între platforme atunci când lucrați cu Flutter

Publicat: 2022-03-10
Rezumat rapid ↬ Când folosesc cadre multiplatforme, oamenii ar putea uita nuanțele fiecăreia dintre platformele pe care doresc să ruleze codul lor. Acest articol își propune să abordeze acest lucru.

Am văzut multă confuzie online în ceea ce privește dezvoltarea web cu Flutter și, de multe ori, din păcate este din motive greșite.

Mai exact, oamenii o confundă uneori cu cadrele web mai vechi pentru platforme mobile (și desktop), care practic erau doar pagini Web care rulau în browsere care rulează într-o aplicație de înveliș.

Era cu adevărat multiplatformă, în sensul că interfețele erau oricum aceleași, deoarece aveai acces doar la interfețele accesibile în mod normal pe Web.

Flutter nu este asta, totuși: rulează nativ pe fiecare platformă și înseamnă că fiecare aplicație rulează exact așa cum ar rula dacă ar fi scrisă în Java/Kotlin sau Objective-C/Swift pe Android și iOS, aproape. Trebuie să știți asta, deoarece acest lucru implică faptul că trebuie să aveți grijă de numeroasele diferențe dintre aceste platforme foarte diverse.

În acest articol, vom vedea câteva dintre aceste diferențe și cum să le depășim. Mai precis, vom vorbi despre diferențele de stocare și UI, care sunt cele care provoacă cel mai adesea confuzie dezvoltatorilor atunci când scriu cod Flutter care doresc să fie multiplatform.

Mai multe după săritură! Continuați să citiți mai jos ↓

Exemplul 1: Depozitare

Am scris recent pe blogul meu despre necesitatea unei abordări diferite pentru stocarea JWT-urilor în aplicațiile web în comparație cu aplicațiile mobile.

Acest lucru se datorează naturii diferite a opțiunilor de stocare ale platformelor și necesității de a cunoaște fiecare și instrumentele lor native de dezvoltare.

Web

Când scrieți o aplicație web, opțiunile de stocare pe care le aveți sunt:

  1. descărcarea/încărcarea fișierelor pe/de pe disc, care necesită interacțiunea utilizatorului și, prin urmare, este potrivită numai pentru fișierele menite să fie citite sau create de utilizator;
  2. utilizarea cookie-urilor, care pot fi sau nu accesibile din JS (în funcție de faptul că sunt sau nu httpOnly ) și sunt trimise automat împreună cu cererile către un anumit domeniu și salvate atunci când vin ca parte a unui răspuns;
  3. folosind JS localStorage și sessionStorage , accesibile de orice JS de pe site, dar numai de la JS care face parte din paginile acelui site.

Mobil

Situația când vine vorba de aplicațiile mobile este complet diferită. Opțiunile de stocare sunt următoarele:

  1. documentele aplicației locale sau stocarea în cache, accesibile prin respectiva aplicație;
  2. alte căi de stocare locală pentru fișierele create de utilizator/lizibile;
  3. NSUserDefaults și, respectiv, SharedPreferences pe iOS și Android pentru stocarea cheie-valoare;
  4. Keychain pe iOS și KeyStore pe Android pentru stocarea în siguranță a oricăror date și chei criptografice.

Dacă nu știți asta, veți face o mizerie cu implementările dvs., deoarece trebuie să știți ce soluție de stocare utilizați de fapt și care sunt avantajele și dezavantajele.

Soluții multiplatforme: o abordare inițială

Utilizarea pachetului Flutter shared_preferences folosește localStorage pe Web, SharedPreferences pe Android și NSUserDefaults pe iOS. Acestea au implicații complet diferite pentru aplicația dvs., mai ales dacă stocați informații sensibile, cum ar fi jetoanele de sesiune: localStorage poate fi citit de client, deci este o problemă dacă sunteți vulnerabil la XSS. Chiar dacă aplicațiile mobile nu sunt cu adevărat vulnerabile la XSS, SharedPreferences și NSUserDefaults nu sunt metode de stocare sigure, deoarece pot fi compromise din partea clientului, deoarece nu sunt stocare sigură și nu sunt criptate. Acest lucru se datorează faptului că sunt destinate preferințelor utilizatorului, așa cum se menționează aici în cazul iOS și aici în documentația Android, când vorbim despre biblioteca de securitate, care este concepută să ofere pachete SharedPreferences special pentru a cripta datele înainte de a le stoca.

Stocare securizată pe mobil

Singurele soluții de stocare sigure pe mobil sunt Keychain și KeyStore pe iOS și, respectiv, Android, în timp ce nu există stocare securizată pe Web .

Keychain și KeyStore sunt totuși foarte diferite în natură: Keychain este o soluție generică de stocare a acreditărilor, în timp ce KeyStore este folosit pentru a stoca (și poate genera) chei criptografice , fie chei simetrice, fie chei publice/private.

Acest lucru înseamnă că, dacă, de exemplu, trebuie să stocați un jeton de sesiune, pe iOS puteți lăsa sistemul de operare să gestioneze partea de criptare și să trimiteți doar jetonul către Keychain , în timp ce pe Android este puțin mai mult o experiență manuală, deoarece aveți nevoie pentru a genera (nu hard-code, asta e rău) o cheie, utilizați-o pentru a cripta jetonul, stocați jetonul criptat în SharedPreferences și stocați cheia în KeyStore .

Există abordări diferite în acest sens, la fel ca majoritatea lucrurilor în securitate, dar cel mai simplu este probabil să utilizați criptarea simetrică, deoarece nu este nevoie de criptare cu cheie publică, deoarece aplicația dvs. criptează și decriptează simbolul.

Evident, nu trebuie să scrieți cod specific platformei mobile care face toate acestea, deoarece există un plugin Flutter care face toate acestea, de exemplu.

Lipsa stocării sigure pe web

Acesta a fost, de fapt, motivul care m-a obligat să scriu această postare. Am scris despre utilizarea acelui pachet pentru a stoca JWT în aplicațiile mobile și oamenii au vrut versiunea web a acestuia, dar, așa cum am spus, nu există stocare sigură pe Web . Nu există.

Asta înseamnă că JWT-ul tău trebuie să fie în aer liber?

Nu deloc. Puteți utiliza cookie-uri httpOnly , nu-i așa? Acestea nu sunt accesibile de JS și sunt trimise numai către serverul dvs. Problema este că acestea sunt întotdeauna trimise la serverul dvs., chiar dacă unul dintre utilizatorii dvs. face clic pe o adresă URL a solicitării GET de pe site-ul web al altcuiva și acea solicitare GET are efecte secundare pe care dvs. sau utilizatorului dvs. nu le vor plăcea. Acest lucru funcționează de fapt și pentru alte tipuri de solicitări, este doar mai complicat. Se numește falsificare de solicitare încrucișată și nu vrei asta. Se numără printre amenințările de securitate web menționate în documentele MDN ale Mozilla, unde puteți găsi o explicație mai completă.

Există metode de prevenire. Cel mai obișnuit este să aibă două jetoane, de fapt: unul dintre ele ajunge la client ca cookie httpOnly , celălalt ca parte a răspunsului. Acesta din urmă trebuie să fie stocat în localStorage și nu în cookie-uri pentru că nu dorim să fie trimis automat la server.

Rezolvarea Ambelor

Ce se întâmplă dacă aveți atât o aplicație mobilă, cât și o aplicație web?

Acest lucru poate fi tratat într-unul din două moduri:

  1. Utilizați același punct final de backend, dar obțineți și trimiteți manual cookie-urile folosind anteturile HTTP legate de cookie-uri;
  2. Creați un punct final de back-end non-Web separat care generează un simbol diferit de unul dintre simbolurile utilizate de aplicația web și apoi permiteți autorizarea JWT obișnuită dacă clientul este capabil să furnizeze simbolul numai pentru dispozitive mobile.

Rularea unui cod diferit pe platforme diferite

Acum, să vedem cum putem rula cod diferit pe platforme diferite pentru a putea compensa diferențele.

Crearea unui plugin Flutter

Mai ales pentru a rezolva problema stocării, o modalitate prin care poți face asta este cu un pachet de plugin: pluginurile oferă o interfață Dart comună și pot rula cod diferit pe platforme diferite, inclusiv codul nativ specific platformei Kotlin/Java sau Swift/Objective-C. . Dezvoltarea pachetelor și a pluginurilor este destul de complexă, dar este explicată în multe locuri de pe Web și în alte părți (de exemplu, în cărțile Flutter), inclusiv în documentația oficială Flutter.

Pentru platformele mobile, de exemplu, există deja un plugin de stocare securizat, și acesta este flutter_secure_storage , pentru care puteți găsi un exemplu de utilizare aici, dar care nu funcționează pe Web, de exemplu.

Pe de altă parte, pentru stocarea cheie-valoare simplă, care funcționează și pe web, există un pachet de pluginuri multiplatformă dezvoltat de Google, numit shared_preferences , care are o componentă specifică web numită shared_preferences_web care utilizează NSUserDefaults , SharedPreferences sau localStorage in functie de platforma.

TargetPlatform pe Flutter

După importarea package:flutter/foundation.dart , puteți compara Theme.of(context).platform cu valorile:

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

și scrieți-vă funcțiile astfel încât, pentru fiecare platformă pe care doriți să o susțineți, să facă lucrul potrivit. Acest lucru va fi util în special pentru următorul exemplu de diferență de platformă, și anume diferențele în modul în care sunt afișate widget-urile pe diferite platforme.

Pentru acel caz de utilizare, în special, există, de asemenea, un plugin rezonabil de popular flutter_platform_widgets , care simplifică dezvoltarea widget-urilor care țin seama de platformă.

Exemplul 2: Diferențele în modul în care este afișat același widget

Nu poți să scrii cod pe mai multe platforme și să pretinzi că un browser, un telefon, un computer și un ceas inteligent sunt același lucru - decât dacă vrei ca aplicația ta pentru Android și iOS să fie WebView și aplicația pentru desktop să fie construită cu Electron . Există o mulțime de motive pentru a nu face asta și nu este scopul acestei piese să vă convingă să utilizați cadre precum Flutter, care să vă mențină aplicația nativă, cu toate avantajele de performanță și experiență de utilizator care vin cu ea, permițându-vă totodată să scrie cod care va fi același pentru toate platformele de cele mai multe ori.

Totuși, acest lucru necesită îngrijire și atenție și cel puțin cunoștințe de bază despre platformele pe care doriți să le susțineți, API-urile lor native reale și toate acestea. Utilizatorii React Native trebuie să acorde și mai multă atenție acestui lucru, deoarece acel cadru folosește widget-urile încorporate ale sistemului de operare, așa că trebuie să acordați și mai multă atenție modului în care arată aplicația testând-o pe ambele platforme, fără a putea comuta între Widget iOS și Material din mers ca și cum ar fi posibil cu Flutter.

Ce se schimbă fără cererea dvs

Există unele aspecte ale interfeței de utilizare a aplicației dvs. care sunt modificate automat atunci când schimbați platforma. Această secțiune menționează și ce se schimbă între Flutter și React Native în acest sens.

Între Android și iOS (Flutter)

Flutter este capabil să redea widget-uri Material pe iOS (și widget-uri Cupertino (asemănător iOS) pe Android), dar ceea ce NU face este să arate exact același lucru pe Android și iOS: Tematica materială se adaptează în special la convențiile fiecărei platforme. .

De exemplu, animațiile de navigare și tranzițiile și fonturile implicite sunt diferite, dar acestea nu afectează atât de mult aplicația dvs.

Ceea ce poate afecta unele dintre alegerile tale când vine vorba de estetică sau UX este faptul că unele elemente statice se schimbă și ele. Mai exact, unele pictograme se schimbă între cele două platforme, titlurile barei de aplicații sunt în mijloc pe iOS și în stânga pe Android (în stânga spațiului disponibil în cazul în care există un buton înapoi sau butonul pentru a deschide un Sertar (explicat aici). în ghidul Material Design și cunoscut și sub numele de meniu hamburger). Iată cum arată o aplicație Material cu sertar pe Android:

imaginea unei aplicații Android care arată unde apare titlul barei de aplicații în aplicațiile Flutter Android Material
Aplicația Material rulează pe Android: titlul AppBar este în partea stângă a spațiului disponibil. (Previzualizare mare)

Și cum arată la fel, foarte simplu, aplicația Material pe iOS:

imaginea unei aplicații iOS care arată unde apare titlul barei de aplicații în aplicațiile Flutter iOS Material
Aplicația Material rulează pe iOS: titlul AppBar este în mijloc. (Previzualizare mare)

Între mobil și web și cu crestături de ecran (Flutter)

Pe Web există o situație puțin diferită, așa cum se menționează și în acest articol Smashing despre Dezvoltarea Web responsabilă cu Flutter: în special, pe lângă faptul că trebuie să optimizați pentru ecrane mai mari și să țineți cont de modul în care oamenii se așteaptă să navigheze prin site-ul dvs. — care este punctul central al acelui articol — trebuie să vă faceți griji cu privire la faptul că uneori widget-urile sunt plasate în afara ferestrei browserului. De asemenea, unele telefoane au crestături în partea superioară a ecranului sau alte impedimente pentru vizualizarea corectă a aplicației dvs. din cauza unui fel de obstacol.

Ambele probleme pot fi evitate prin împachetarea widget-urilor într-un widget SafeArea , care este un tip special de widget de umplutură care se asigură că widget-urile dvs. se încadrează într-un loc unde pot fi afișate fără ca nimic să împiedice abilitatea utilizatorilor de a le vedea. fie că este vorba de o constrângere hardware sau software.

În React Native

React Native necesită mult mai multă atenție și o cunoaștere mult mai profundă a fiecărei platforme, pe lângă faptul că vă cere să rulați simulatorul iOS, precum și cel puțin emulatorul Android pentru a vă putea testa aplicația pe ambele platforme: nu este la fel și își convertește elementele JavaScript UI în widget-uri specifice platformei. Cu alte cuvinte, aplicațiile dvs. React Native vor arăta întotdeauna ca iOS - cu elemente Cupertino UI, așa cum sunt numite uneori - și aplicațiile dvs. Android vor arăta întotdeauna ca aplicațiile Android Material Design obișnuite, deoarece utilizează widget-urile platformei.

Diferența aici este că Flutter își redă widget-urile cu propriul motor de randare la nivel scăzut, ceea ce înseamnă că puteți testa ambele versiuni de aplicație pe o singură platformă.

Depășirea acestei probleme

Dacă nu căutați ceva foarte specific, aplicația dvs. ar trebui să arate diferit pe diferite platforme, altfel unii dintre utilizatorii dvs. vor fi nemulțumiți.

La fel cum nu ar trebui să trimiteți pur și simplu o aplicație mobilă pe web (cum am scris în postarea Smashing menționată mai sus), nu ar trebui să trimiteți o aplicație plină de widget-uri Cupertino utilizatorilor de Android, de exemplu, pentru că va fi confuz pentru Cea mai mare parte. Pe de altă parte, a avea șansa de a rula efectiv o aplicație care are widget-uri care sunt destinate unei alte platforme vă permite să testați aplicația și să o afișați oamenilor în ambele versiuni fără a fi necesar să utilizați două dispozitive pentru asta.

Cealaltă parte: utilizarea widgeturilor greșite din motivele corecte

Dar asta înseamnă, de asemenea, că puteți face cea mai mare parte a dezvoltării Flutter pe o stație de lucru Linux sau Windows fără a sacrifica experiența utilizatorilor dvs. iOS, apoi doar să creați aplicația pentru cealaltă platformă și să nu vă faceți griji că o testați temeinic.

Pasii urmatori

Cadrele multiplatforme sunt minunate, dar vă transferă responsabilitatea dvs., dezvoltator, de a înțelege cum funcționează fiecare platformă și de a vă asigura că aplicația dvs. se adaptează și este plăcut de utilizat pentru utilizatori. Alte lucruri mici de luat în considerare pot fi, de exemplu, folosirea unor descrieri diferite pentru ceea ce ar putea fi în esență același lucru dacă există convenții diferite pe platforme diferite.

Este grozav să nu fii nevoit să construiești cele două (sau mai multe) aplicații separat folosind limbi diferite, dar totuși trebuie să ții minte că, în esență, construiești mai mult de o aplicație și asta necesită să te gândești la fiecare dintre aplicațiile pe care le construiești. .

Resurse suplimentare

  • Site-ul web Flutter Gallery și aplicația Android, care prezintă utilizarea widget-urilor Flutter tipice diferitelor platforme și agnosticismul acestora
  • Documentația Flutter API pe TargetPlatform
  • Flutter documentație privind crearea de pachete și pluginuri
  • Flutter documentație privind adaptările platformei
  • Documentația MDN despre cookie-uri