Utilizarea Flutter de la Google pentru o dezvoltare mobilă cu adevărat multiplatformă
Publicat: 2022-03-10Flutter este un cadru de dezvoltare mobilă multiplatformă, open source de la Google. Permite crearea de aplicații frumoase și de înaltă performanță pentru iOS și Android dintr-o singură bază de cod. Este, de asemenea, platforma de dezvoltare pentru viitorul sistem de operare Fuchsia de la Google. În plus, este proiectat astfel încât să poată fi adus pe alte platforme, prin intermediul sistemelor de încorporare personalizate ale motorului Flutter.
De ce a fost creat Flutter și de ce ar trebui să îl folosiți
Seturile de instrumente multiplatforme au adoptat istoric una dintre cele două abordări:
- Ei împachetează o vizualizare web într-o aplicație nativă și construiesc aplicația ca și cum ar fi un site web.
- Acestea înglobează controalele native ale platformei și oferă o abstractizare multiplatformă asupra lor.
Flutter adoptă o abordare diferită în încercarea de a îmbunătăți dezvoltarea mobilă. Oferă un cadru pe care dezvoltatorii de aplicații lucrează și un motor cu un runtime portabil pentru a găzdui aplicațiile. Cadrul se bazează pe biblioteca grafică Skia, oferind widget-uri care sunt de fapt redate, spre deosebire de a fi doar pachete de comenzi native.
Această abordare oferă flexibilitatea de a construi o aplicație multiplatformă într-o manieră complet personalizată, așa cum o oferă opțiunea web wrapper, dar în același timp oferind performanțe fără probleme. Între timp, biblioteca bogată de widget-uri care vine cu Flutter, împreună cu o mulțime de widget-uri open-source, o fac o platformă foarte bogată în funcții pentru a lucra. Mai simplu spus, Flutter este cel mai apropiat lucru pe care l-au avut dezvoltatorii de telefonie mobilă pentru dezvoltarea multiplatformă, fără compromisuri.
Lance
Aplicațiile Flutter sunt scrise în Dart, care este un limbaj de programare dezvoltat inițial de Google. Dart este un limbaj orientat pe obiecte care acceptă atât compilarea anticipată, cât și compilarea just-in-time, făcându-l foarte potrivit pentru construirea de aplicații native, oferind în același timp un flux de lucru eficient de dezvoltare cu reîncărcarea la cald a Flutter. Flutter s-a mutat recent și la versiunea 2.0 a Dart.
Limbajul Dart oferă multe dintre caracteristicile văzute în alte limbi, inclusiv colectarea gunoiului, async-wait, tastare puternică, generice, precum și o bibliotecă standard bogată.
Dart oferă o intersecție de funcții care ar trebui să fie familiare dezvoltatorilor care provin dintr-o varietate de limbi, cum ar fi C#, JavaScript, F#, Swift și Java. În plus, Dart se poate compila în Javascript. Combinat cu Flutter, acest lucru permite partajarea codului pe platformele web și mobile.
Cronologia istorică a evenimentelor
- aprilie 2015
Flutter (inițial cu numele de cod Sky) prezentat la Dart Developer Summit - noiembrie 2015
Sky redenumit în Flutter - februarie 2018
Flutter beta 1 anunțat la Mobile World Congress 2018 - aprilie 2018
Flutter beta 2 a anunțat - mai 2018
Flutter beta 3 a anunțat la Google I/O. Google anunță că Flutter este pregătit pentru aplicații de producție
Comparație cu alte platforme de dezvoltare
Apple/Android Nativ
Aplicațiile native oferă cea mai mică frecare în adoptarea de noi funcții. Ei tind să aibă experiențe de utilizator mai în ton cu platforma dată, deoarece aplicațiile sunt construite folosind controale de la furnizorii de platforme înșiși (Apple sau Google) și urmează adesea liniile directoare de proiectare stabilite de acești furnizori. În cele mai multe cazuri, aplicațiile native vor avea performanțe mai bune decât cele construite cu oferte multiplatformă, deși diferența poate fi neglijabilă în multe cazuri, în funcție de tehnologia cross-platformă de bază.
Un mare avantaj pe care îl au aplicațiile native este că pot adopta tehnologii noi, pe care Apple și Google le creează în versiune beta imediat, dacă se dorește, fără a fi nevoie să aștepte integrarea unei terțe părți. Principalul dezavantaj al construirii de aplicații native este lipsa reutilizarii codului pe platforme, ceea ce poate face dezvoltarea costisitoare dacă vizează iOS și Android.
Reacționează nativ
React Native permite crearea aplicațiilor native folosind JavaScript. Controalele reale pe care le folosește aplicația sunt controalele native ale platformei, astfel încât utilizatorul final are senzația unei aplicații native. Pentru aplicațiile care necesită personalizare dincolo de ceea ce oferă abstractizarea lui React Native, ar putea fi în continuare necesară dezvoltarea nativă. În cazurile în care cantitatea de personalizare necesară este substanțială, beneficiul lucrului în stratul de abstractizare al React Native scade până la punctul în care, în unele cazuri, dezvoltarea aplicației în mod nativ ar fi mai benefică.
Xamarin
Când discutăm despre Xamarin, există două abordări diferite care trebuie evaluate. Pentru abordarea lor cea mai multiplatformă, există Xamarin.Forms. Deși tehnologia este foarte diferită de React Native, din punct de vedere conceptual, oferă o abordare similară prin faptul că abstrage controalele native. De asemenea, are dezavantaje similare în ceea ce privește personalizarea.
În al doilea rând, există ceea ce mulți numesc Xamarin-clasic. Această abordare folosește produsele Xamarin iOS și Android în mod independent pentru a construi caracteristici specifice platformei, la fel ca atunci când utilizați direct Apple/Android nativ, folosind doar C# sau F# în cazul Xamarin. Avantajul Xamarin este că codul care nu este specific platformei, lucruri precum rețelele, accesul la date, serviciile web etc. pot fi partajate.
Spre deosebire de aceste alternative, Flutter încearcă să ofere dezvoltatorilor o soluție multiplatformă mai completă, cu reutilizare a codului, interfețe de utilizator fluide și de înaltă performanță și instrumente excelente.
Prezentare generală a unei aplicații Flutter
Crearea unei aplicații
După instalarea Flutter, crearea unei aplicații cu Flutter este la fel de simplă ca fie deschiderea unei linii de comandă și introducerea flutter create [app_name]
, selectând comanda „Flutter: New Project” în VS Code sau selectând „Start a new Flutter Project” în Android Studio sau IntelliJ.
Indiferent dacă alegeți să utilizați un IDE sau linia de comandă împreună cu editorul preferat, noul șablon de aplicație Flutter vă oferă un bun punct de plecare pentru o aplicație.
Aplicația aduce pachetul flutter
/ material.dart
pentru a oferi unele schele de bază pentru aplicație, cum ar fi o bară de titlu, pictograme materiale și tematică. De asemenea, configurează un widget cu stare pentru a demonstra cum se actualizează interfața cu utilizatorul atunci când starea aplicației se modifică.

Opțiuni de scule
Flutter oferă o flexibilitate incredibilă în ceea ce privește sculele. Aplicațiile pot fi dezvoltate la fel de ușor din linia de comandă împreună cu orice editor, așa cum pot fi dintr-un IDE acceptat, cum ar fi VS Code, Android Studio sau IntelliJ. Abordarea de luat depinde în mare măsură de preferințele dezvoltatorului.
Android Studio oferă cele mai multe funcții, cum ar fi un Flutter Inspector pentru a analiza widget-urile unei aplicații care rulează și pentru a monitoriza performanța aplicației. De asemenea, oferă câteva refactorizări care sunt convenabile atunci când se dezvoltă o ierarhie de widget-uri.
VS Code oferă o experiență de dezvoltare mai ușoară, deoarece tinde să pornească mai repede decât Android Studio/IntelliJ. Fiecare IDE oferă ajutoare de editare încorporate, cum ar fi completarea codului, permițând explorarea diferitelor API-uri, precum și un suport bun de depanare.
Linia de comandă este, de asemenea, bine acceptată prin comanda flutter
, ceea ce facilitează crearea, actualizarea și lansarea unei aplicații fără nicio altă dependență de instrumente în afara unui editor.

Reîncărcare la cald
Indiferent de instrumente, Flutter menține un suport excelent pentru reîncărcarea la cald a unei aplicații. Acest lucru permite ca o aplicație care rulează să fie modificată în multe cazuri, menținând starea, fără a fi nevoie să oprească aplicația, să reconstruiască și să redistribuie.
Reîncărcarea la cald crește dramatic eficiența dezvoltării, permițând o iterație mai rapidă. Cu adevărat, face ca platforma să fie o plăcere să lucrezi.
Testare
Flutter include un utilitar WidgetTester
pentru a interacționa cu widget-urile dintr-un test. Noul șablon de aplicație include un exemplu de test pentru a demonstra cum să îl utilizați atunci când creați un test, după cum se arată mai jos:
// Test included with the new Flutter application template import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:myapp/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(new MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
Utilizarea pachetelor și pluginurilor
Flutter abia începe, dar există deja un ecosistem bogat pentru dezvoltatori: o multitudine de pachete și pluginuri sunt deja disponibile pentru dezvoltatori.
Pentru a adăuga un pachet sau un plugin, includeți pur și simplu dependența în fișierul pubspec.yaml din directorul rădăcină al aplicației. Apoi rulați flutter packages get
fie din linia de comandă, fie prin IDE, iar instrumentele lui Flutter vor aduce toate dependențele necesare.
De exemplu, pentru a utiliza popularul plugin de selectare a imaginilor pentru Flutter, pubspec.yaml trebuie doar să îl listeze ca dependență, astfel:
dependencies: image_picker: "^0.4.1"
Apoi rularea flutter packages get
aduce tot ce aveți nevoie pentru a le folosi, după care poate fi importată și folosită în Dart:
import 'package:image_picker/image_picker.dart';
Widgeturi
Totul în Flutter este un widget. Aceasta include elemente de interfață cu utilizatorul, cum ar fi ListView
, TextBox
și Image
, precum și alte părți ale cadrului, inclusiv aspect, animație, recunoaștere a gesturilor și teme, pentru a numi doar câteva.
Făcând ca totul să fie un widget, întreaga aplicație, care de altfel este și un widget, poate fi reprezentată în ierarhia widgetului. Având o arhitectură în care totul este un widget, arată clar de unde provin anumite atribute și comportament aplicate unei porțiuni a unei aplicații. Acesta este diferit de majoritatea celorlalte cadre de aplicație, care asociază proprietăți și comportament în mod inconsecvent, uneori atașându-le de la alte componente dintr-o ierarhie și alteori pe controlul în sine.
Exemplu de Widget UI simplu
Punctul de intrare într-o aplicație Flutter este funcția principală. Pentru a pune pe ecran un widget pentru un element de interfață cu utilizatorul, în main()
apelați runApp()
și transmiteți-i widgetul care va servi ca rădăcină a ierarhiei widget-ului.
import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }
Rezultă un widget Container
albastru deschis care umple ecranul:

Widgeturi fără stat vs. cu stat
Widgeturile vin în două variante: fără stat și cu stare. Widgeturile fără stat nu își schimbă conținutul după ce sunt create și inițializate, în timp ce widgeturile cu stare mențin o stare care se poate modifica în timpul rulării aplicației, de exemplu, ca răspuns la interacțiunea utilizatorului.

În acest exemplu, un widget FlatButton
și un widget Text
sunt desenate pe ecran. Widgetul Text
începe cu un String
implicit pentru starea sa. Apăsarea butonului are ca rezultat o schimbare a stării care va determina actualizarea widget-ului Text
, afișând un String
nou.
Pentru a încapsula un widget, creați o clasă care derivă fie din StatelessWidget
, fie StatefulWidget
. De exemplu, Container
albastru deschis poate fi scris după cum urmează:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }
Flutter va apela metoda de construire a widget-ului atunci când este inserat în arborele widget, astfel încât această porțiune a interfeței de utilizare să poată fi redată.
Pentru un widget cu stare, derivă din StatefulWidget
:
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
Widgeturile cu state returnează o clasă State
care este responsabilă pentru construirea arborelui widget pentru o anumită stare. Când starea se schimbă, porțiunea asociată a arborelui widget este reconstruită.
În următorul cod, clasa State
actualizează un String
atunci când se face clic pe un buton:
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
Starea este actualizată într-o funcție care este transmisă setState()
. Când setState()
este apelată, această funcție poate seta orice stare internă, cum ar fi șirul din acest exemplu. Apoi, metoda de build
va fi apelată, actualizând arborele widget-ului cu stare.

Rețineți, de asemenea, utilizarea widget-ului Directionality
pentru a seta direcția textului pentru orice widget-uri din subarborele său care necesită acest lucru, cum ar fi widget-urile Text
. Exemplele de aici sunt coduri de construcție de la zero, așa că Directionality
este necesară undeva în sus în ierarhia widgetului. Cu toate acestea, folosind widget-ul MaterialApp
, cum ar fi șablonul implicit de aplicație, setează direcția textului în mod implicit.
Aspect
Funcția runApp
umflă widgetul pentru a umple ecranul în mod implicit. Pentru a controla aspectul widget-ului, Flutter oferă o varietate de widget-uri de aspect. Există widget-uri pentru a realiza aspecte care aliniază widget-urile copil pe verticală sau orizontală, extind widget-urile pentru a umple un anumit spațiu, limitează widget-urile la o anumită zonă, le centrează pe ecran și permit widget-urilor să se suprapună.
Două widget-uri utilizate în mod obișnuit sunt Row
și Column
. Aceste widget-uri realizează layout-uri pentru a-și afișa widget-urile secundare orizontal (Rând) sau vertical (Coloană).
Utilizarea acestor widget-uri de aspect implică pur și simplu împachetarea lor în jurul unei liste de widget-uri copii. mainAxisAlignment
controlează modul în care widget-urile sunt poziționate de-a lungul axei aspectului, fie centrate, la început, la sfârșit, sau cu diferite opțiuni de spațiere.
Următorul cod arată cum să aliniați mai multe widget-uri copil într-un Row
sau Column
:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Row( //change to Column for vertical layout mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.android, size: 30.0), Icon(Icons.pets, size: 10.0), Icon(Icons.stars, size: 75.0), Icon(Icons.rowing, size: 25.0), ], ); } }

Răspunzând la atingere
Interacțiunea tactilă este gestionată cu gesturi, care sunt încapsulate în clasa GestureDetector
. Deoarece este, de asemenea, un widget, adăugarea recunoașterii gesturilor este la fel de ușoară ca și împachetarea widgeturilor pentru copii într-un GestureDetector
.
De exemplu, pentru a adăuga manipularea atingerii la o Icon
, faceți-o un copil al unui GestureDetector
și setați manevrele detectorului pentru gesturile dorite de capturat.
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( onTap: () => print('you tapped the star'), onDoubleTap: () => print('you double tapped the star'), onLongPress: () => print('you long pressed the star'), child: Icon(Icons.stars, size: 200.0), ); } }
În acest caz, când se efectuează o atingere, o atingere dublă sau o apăsare lungă pe pictogramă, textul asociat este tipărit:
To hot reload your app on the fly, press "r". To restart the app entirely, press "R". An Observatory debugger and profiler on iPhone X is available at: https://127.0.0.1:8100/ For a more detailed help message, press "h". To quit, press "q". flutter: you tapped the star flutter: you double tapped the star flutter: you long pressed the star
Pe lângă gesturile simple de atingere, există o mulțime de instrumente de recunoaștere, pentru orice, de la deplasare și scalare până la glisare. Acestea fac foarte ușor să construiți aplicații interactive.
Pictura
Flutter oferă, de asemenea, o varietate de widget-uri cu care să picteze, inclusiv cele care modifică opacitatea, setează trasee de tăiere și aplică decorațiuni. Acceptă chiar și pictura personalizată prin widget-ul CustomPaint
și clasele asociate CustomPainter
și Canvas
.
Un exemplu de widget de pictură este DecoratedBox
, care poate picta o BoxDecoration
pe ecran. Următorul exemplu arată cum să utilizați acest lucru pentru a umple ecranul cu o umplere în gradient:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new DecoratedBox( child: Icon(Icons.stars, size: 200.0), decoration: new BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } }

Animaţie
Flutter include o clasă AnimationController
care controlează redarea animației în timp, inclusiv pornirea și oprirea unei animații, precum și variarea valorilor unei animații. În plus, există un widget AnimatedBuilder
care permite construirea unei animații împreună cu un AnimationController
.
Orice widget, cum ar fi steaua decorată prezentată mai devreme, poate avea proprietățile animate. De exemplu, refactorizarea codului într-un StatefulWidget
, deoarece animarea este o schimbare de stare și trecerea unui AnimationController
la clasa State
permite ca valoarea animată să fie utilizată pe măsură ce widget-ul este construit.
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
În acest caz, valoarea este folosită pentru a varia dimensiunea widget-ului. Funcția de builder
este apelată ori de câte ori valoarea animată se schimbă, ceea ce face ca dimensiunea stelei să varieze peste 750 ms, creând o scară în vigoare:

Utilizarea caracteristicilor native
Canalele platformei
Pentru a oferi acces la API-urile platformei native pe Android și iOS, o aplicație Flutter poate folosi canalele platformei. Acestea permit codului Flutter Dart să trimită mesaje către aplicația de găzduire iOS sau Android. Multe dintre pluginurile open-source care sunt disponibile sunt create folosind mesageria pe canalele platformei. Pentru a afla cum să lucrați cu canalele platformei, documentația Flutter include un document bun care demonstrează accesarea API-urilor native ale bateriei.
Concluzie
Chiar și în versiune beta, Flutter oferă o soluție excelentă pentru construirea de aplicații multiplatforme. Cu instrumentele sale excelente și reîncărcarea la cald, oferă o experiență de dezvoltare foarte plăcută. Bogăția de pachete open-source și documentația excelentă fac să începeți ușor. În așteptare, dezvoltatorii Flutter vor putea să vizeze Fuchsia pe lângă iOS și Android. Având în vedere extensibilitatea arhitecturii motorului, nu m-ar surprinde să văd că Flutter aterizează și pe o varietate de alte platforme. Cu o comunitate în creștere, este un moment grozav pentru a intra.
Pasii urmatori
- Instalați Flutter
- Tur de limbă Dart
- Flutter Codelabs
- Curs Flutter Udacity
- Cod sursă articol