Utilizzo di Flutter di Google per uno sviluppo mobile davvero multipiattaforma

Pubblicato: 2022-03-10
Riepilogo rapido ↬ Flutter rende la creazione di applicazioni mobili multipiattaforma un gioco da ragazzi. Questo articolo introduce Flutter, lo confronta con altre piattaforme di sviluppo mobile e mostra come utilizzarlo per iniziare a creare app.

Flutter è un framework di sviluppo mobile multipiattaforma open source di Google. Consente di creare applicazioni belle e ad alte prestazioni per iOS e Android da un'unica base di codice. È anche la piattaforma di sviluppo per il prossimo sistema operativo Fuchsia di Google. Inoltre, è progettato in modo da poter essere portato su altre piattaforme, tramite incorporatori di motori Flutter personalizzati.

Perché è stato creato Flutter e perché dovresti usarlo

I toolkit multipiattaforma hanno storicamente adottato uno dei due approcci seguenti:

  • Racchiudono una visualizzazione Web in un'app nativa e creano l'applicazione come se fosse un sito Web.
  • Racchiudono i controlli della piattaforma nativi e forniscono su di essi un'astrazione multipiattaforma.

Flutter adotta un approccio diverso nel tentativo di migliorare lo sviluppo mobile. Fornisce un framework per il lavoro degli sviluppatori di applicazioni e un motore con un runtime portatile per ospitare le applicazioni. Il framework si basa sulla libreria grafica di Skia, fornendo widget che sono effettivamente renderizzati, invece di essere solo wrapper sui controlli nativi.

Questo approccio offre la flessibilità di creare un'applicazione multipiattaforma in un modo completamente personalizzato come l'opzione Web wrapper offre, ma allo stesso tempo offre prestazioni fluide. Nel frattempo, la ricca libreria di widget fornita con Flutter, insieme a una vasta gamma di widget open source, la rende una piattaforma molto ricca di funzionalità con cui lavorare. In parole povere, Flutter è la cosa più vicina agli sviluppatori di dispositivi mobili per lo sviluppo multipiattaforma con poco o nessun compromesso.

Altro dopo il salto! Continua a leggere sotto ↓

Dardo

Le applicazioni Flutter sono scritte in Dart, un linguaggio di programmazione originariamente sviluppato da Google. Dart è un linguaggio orientato agli oggetti che supporta la compilazione sia anticipata che just-in-time, il che lo rende particolarmente adatto per la creazione di applicazioni native, fornendo al contempo un flusso di lavoro di sviluppo efficiente con il ricaricamento a caldo di Flutter. Flutter è recentemente passato anche alla versione 2.0 di Dart.

Il linguaggio Dart offre molte delle funzionalità viste in altri linguaggi, tra cui Garbage Collection, async-await, tipizzazione avanzata, generici e una ricca libreria standard.

Dart offre un'intersezione di funzionalità che dovrebbero essere familiari agli sviluppatori provenienti da una varietà di linguaggi, come C#, JavaScript, F#, Swift e Java. Inoltre, Dart può compilare in Javascript. In combinazione con Flutter, consente di condividere il codice su piattaforme Web e mobili.

Cronologia storica degli eventi

  • aprile 2015
    Flutter (originariamente nome in codice Sky) mostrato al Dart Developer Summit
  • novembre 2015
    Sky rinominato Flutter
  • Febbraio 2018
    Flutter beta 1 annunciata al Mobile World Congress 2018
  • aprile 2018
    Annunciata la beta 2 di Flutter
  • maggio 2018
    Flutter beta 3 annunciata al Google I/O. Google annuncia che Flutter è pronto per le app di produzione

Confronto con altre piattaforme di sviluppo

Nativo Apple/Android

Le applicazioni native offrono il minor attrito nell'adozione di nuove funzionalità. Tendono ad avere esperienze utente più in sintonia con la piattaforma data poiché le applicazioni sono costruite utilizzando i controlli degli stessi fornitori di piattaforme (Apple o Google) e spesso seguono le linee guida di progettazione stabilite da questi fornitori. Nella maggior parte dei casi, le applicazioni native funzioneranno meglio di quelle create con offerte multipiattaforma, sebbene la differenza possa essere trascurabile in molti casi a seconda della tecnologia multipiattaforma sottostante.

Un grande vantaggio delle applicazioni native è che possono adottare tecnologie nuove di zecca che Apple e Google creano immediatamente in versione beta, se lo si desidera, senza dover attendere l'integrazione di terze parti. Lo svantaggio principale della creazione di applicazioni native è la mancanza di riutilizzo del codice su più piattaforme, che può rendere costoso lo sviluppo se si utilizza iOS e Android.

Reagire nativo

React Native consente di creare applicazioni native utilizzando JavaScript. I controlli effettivi utilizzati dall'applicazione sono controlli della piattaforma nativi, quindi l'utente finale ha la sensazione di un'app nativa. Per le app che richiedono una personalizzazione oltre a ciò che fornisce l'astrazione di React Native, potrebbe essere comunque necessario lo sviluppo nativo. Nei casi in cui la quantità di personalizzazione richiesta è sostanziale, il vantaggio di lavorare all'interno del livello di astrazione di React Native diminuisce al punto che in alcuni casi lo sviluppo dell'app in modo nativo sarebbe più vantaggioso.

Xamarin

Quando si discute di Xamarin, ci sono due diversi approcci che devono essere valutati. Per il loro approccio più multipiattaforma, c'è Xamarin.Forms. Sebbene la tecnologia sia molto diversa da React Native, concettualmente offre un approccio simile in quanto astrae i controlli nativi. Allo stesso modo, ha aspetti negativi simili per quanto riguarda la personalizzazione.

In secondo luogo, c'è ciò che molti chiamano Xamarin-classico. Questo approccio usa i prodotti iOS e Android di Xamarin in modo indipendente per creare funzionalità specifiche della piattaforma, proprio come quando si usa direttamente il nativo Apple/Android, usando solo C# o F# nel caso Xamarin. Il vantaggio di Xamarin è che è possibile condividere codice non specifico della piattaforma, cose come rete, accesso ai dati, servizi Web e così via.

A differenza di queste alternative, Flutter cerca di offrire agli sviluppatori una soluzione multipiattaforma più completa, con riutilizzo del codice, interfacce utente fluide e ad alte prestazioni e strumenti eccellenti.

Panoramica di un'app Flutter

Creazione di un'app

Dopo aver installato Flutter, creare un'app con Flutter è semplice come aprire una riga di comando e inserire flutter create [app_name] , selezionando il comando "Flutter: New Project" in VS Code o selezionando "Start a new Flutter Project" in Android Studio o IntelliJ.

Indipendentemente dal fatto che tu scelga di utilizzare un IDE o la riga di comando insieme al tuo editor preferito, il nuovo modello di applicazione Flutter ti offre un buon punto di partenza per un'applicazione.

L'applicazione introduce il pacchetto flutter / material.dart per offrire alcune impalcature di base per l'app, come una barra del titolo, icone dei materiali e temi. Imposta anche un widget con stato per dimostrare come aggiornare l'interfaccia utente quando lo stato dell'applicazione cambia.

Nuovo modello di applicazione Flutter
La nuova applicazione Flutter in esecuzione su iOS e Android. (Grande anteprima)

Opzioni di attrezzaggio

Flutter offre un'incredibile flessibilità per quanto riguarda gli utensili. Le applicazioni possono essere sviluppate altrettanto facilmente dalla riga di comando insieme a qualsiasi editor, così come possono essere da un IDE supportato come VS Code, Android Studio o IntelliJ. L'approccio da adottare dipende in gran parte dalle preferenze dello sviluppatore.

Android Studio offre la maggior parte delle funzionalità, come Flutter Inspector per analizzare i widget di un'applicazione in esecuzione e monitorare le prestazioni dell'applicazione. Offre anche diversi refactoring utili quando si sviluppa una gerarchia di widget.

VS Code offre un'esperienza di sviluppo più leggera in quanto tende ad avviarsi più velocemente di Android Studio/IntelliJ. Ogni IDE offre aiutanti di modifica integrati, come il completamento del codice, consentendo l'esplorazione di varie API e un buon supporto per il debug.

La riga di comando è anche ben supportata tramite il comando flutter , che semplifica la creazione, l'aggiornamento e l'avvio di un'applicazione senza altre dipendenze dagli strumenti oltre a un editor.

Utensili svolazzanti
Gli strumenti Flutter supportano una varietà di ambienti. (Grande anteprima)

Ricarica a caldo

Indipendentemente dagli strumenti, Flutter mantiene un eccellente supporto per il ricaricamento a caldo di un'applicazione. Ciò consente in molti casi di modificare un'applicazione in esecuzione, mantenendo lo stato, senza dover arrestare l'app, ricostruirla e ridistribuirla.

La ricarica a caldo aumenta notevolmente l'efficienza dello sviluppo consentendo un'iterazione più rapida. Rende davvero piacevole lavorare con la piattaforma.

Test

Flutter include un'utilità WidgetTester per interagire con i widget di un test. Il nuovo modello di applicazione include un test di esempio per dimostrare come utilizzarlo durante la creazione di un test, come mostrato di seguito:

 // 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); }); }

Utilizzo di pacchetti e plugin

Flutter è solo all'inizio, ma esiste già un ricco ecosistema di sviluppatori: una miriade di pacchetti e plugin sono già disponibili per gli sviluppatori.

Per aggiungere un pacchetto o un plugin, includi semplicemente la dipendenza nel file pubspec.yaml nella directory principale dell'applicazione. Quindi esegui i flutter packages get dalla riga di comando o tramite l'IDE e gli strumenti di Flutter introdurranno tutte le dipendenze richieste.

Ad esempio, per utilizzare il popolare plug-in per la selezione delle immagini per Flutter, pubspec.yaml deve solo elencarlo come una dipendenza in questo modo:

 dependencies: image_picker: "^0.4.1"

Quindi l'esecuzione di flutter packages get porta dentro tutto ciò di cui hai bisogno per farne uso, dopodiché può essere importato e utilizzato in Dart:

 import 'package:image_picker/image_picker.dart';

Widget

Tutto in Flutter è un widget. Ciò include elementi dell'interfaccia utente, come ListView , TextBox e Image , nonché altre parti del framework, inclusi layout, animazione, riconoscimento dei gesti e temi, solo per citarne alcuni.

Poiché tutto è un widget, l'intera applicazione, che per inciso è anche un widget, può essere rappresentata all'interno della gerarchia dei widget. Avere un'architettura in cui tutto è un widget chiarisce da dove provengono determinati attributi e comportamenti applicati a una parte di un'app. Questo è diverso dalla maggior parte degli altri framework applicativi, che associano proprietà e comportamento in modo incoerente, a volte collegandoli da altri componenti in una gerarchia e altre volte sul controllo stesso.

Esempio di widget dell'interfaccia utente semplice

Il punto di accesso a un'applicazione Flutter è la funzione principale. Per mettere sullo schermo un widget per un elemento dell'interfaccia utente, in main() chiama runApp() e passagli il widget che fungerà da radice della gerarchia dei widget.

 import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }

Ne risulta un widget Container azzurro che riempie lo schermo:

Applicazione di sfarfallio minimo
Applicazione Flutter minima con un singolo contenitore (Anteprima grande)

Widget Stateless vs Stateful

I widget sono disponibili in due versioni: stateless e stateful. I widget stateless non cambiano il loro contenuto dopo essere stati creati e inizializzati, mentre i widget stateful mantengono uno stato che può cambiare durante l'esecuzione dell'applicazione, ad esempio in risposta all'interazione dell'utente.

In questo esempio, sullo schermo vengono disegnati un widget FlatButton e un widget di Text . Il widget Text inizia con una String predefinita per il suo stato. Premendo il pulsante si verifica un cambio di stato che comporterà l'aggiornamento del widget Text , visualizzando un nuovo String .

Per incapsulare un widget, creare una classe che derivi da StatelessWidget o StatefulWidget . Ad esempio, il Container azzurro potrebbe essere scritto come segue:

 class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }

Flutter chiamerà il metodo di compilazione del widget quando viene inserito nell'albero dei widget in modo che questa parte dell'interfaccia utente possa essere renderizzata.

Per un widget con stato, deriva da StatefulWidget :

 class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } } class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }

I widget con stato restituiscono una classe State che è responsabile della creazione dell'albero dei widget per un determinato stato. Quando lo stato cambia, la parte associata dell'albero dei widget viene ricostruita.

Nel codice seguente, la classe State aggiorna una String quando si fa clic su un pulsante:

 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)), ], ) ) ) ); } }

Lo stato viene aggiornato in una funzione che viene passata a setState() . Quando viene chiamato setState() , questa funzione può impostare qualsiasi stato interno, come la stringa in questo esempio. Quindi, verrà chiamato il metodo build , aggiornando l'albero del widget con stato.

Cambio di stato
Gestione di un cambiamento di stato dall'interazione dell'utente (anteprima grande)

Nota anche l'uso del widget Directionality per impostare la direzione del testo per tutti i widget nella sua sottostruttura che lo richiedono, come i widget Text . Gli esempi qui sono la creazione di codice da zero, quindi la Directionality è necessaria da qualche parte nella gerarchia dei widget. Tuttavia, l'utilizzo del widget MaterialApp , ad esempio con il modello di applicazione predefinito, imposta la direzione del testo in modo implicito.

Disposizione

La funzione runApp gonfia il widget per riempire lo schermo per impostazione predefinita. Per controllare il layout dei widget, Flutter offre una varietà di widget di layout. Esistono widget per eseguire layout che allineano i widget figlio verticalmente o orizzontalmente, espandono i widget per riempire uno spazio particolare, limitano i widget a una determinata area, li centrano sullo schermo e consentono ai widget di sovrapporsi.

Due widget comunemente usati sono Row e Column . Questi widget eseguono layout per visualizzare i loro widget figlio orizzontalmente (Riga) o verticalmente (Colonna).

L'uso di questi widget di layout implica semplicemente il loro avvolgimento attorno a un elenco di widget figlio. mainAxisAlignment controlla il modo in cui i widget sono posizionati lungo l'asse del layout, centrato, all'inizio, alla fine o con varie opzioni di spaziatura.

Il codice seguente mostra come allineare diversi widget figlio in una Row o in una 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), ], ); } } 
Widget Riga
Widget riga che mostra il layout orizzontale (anteprima grande)

Rispondere al tocco

L'interazione tattile viene gestita con i gesti, che sono incapsulati nella classe GestureDetector . Poiché è anche un widget, aggiungere il riconoscimento dei gesti è facile come avvolgere i widget figlio in un GestureDetector .

Ad esempio, per aggiungere la gestione del tocco a Icon , rendila figlia di un GestureDetector e imposta i gestori del rilevatore per i gesti desiderati da acquisire.

 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), ); } }

In questo caso, quando si esegue un tocco, un doppio tocco o una pressione prolungata sull'icona, viene stampato il testo associato:

 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

Oltre ai semplici gesti di tocco, c'è una vasta gamma di riconoscitori, per qualsiasi cosa, dal panning e ridimensionamento, al trascinamento. Questi rendono molto facile creare applicazioni interattive.

La pittura

Flutter offre anche una varietà di widget con cui dipingere, inclusi quelli che modificano l'opacità, impostano tracciati di ritaglio e applicano decorazioni. Supporta anche la pittura personalizzata tramite il widget CustomPaint e le classi CustomPainter e Canvas associate.

Un esempio di widget di pittura è DecoratedBox , che può dipingere un BoxDecoration sullo schermo. L'esempio seguente mostra come utilizzarlo per riempire lo schermo con un riempimento sfumato:

 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 ), ), ); } } 
Sfondo sfumato
Dipingere uno sfondo sfumato (Anteprima grande)

Animazione

Flutter include una classe AnimationController che controlla la riproduzione dell'animazione nel tempo, incluso l'avvio e l'arresto di un'animazione, nonché la variazione dei valori in un'animazione. Inoltre, è disponibile un widget AnimatedBuilder che consente di costruire un'animazione insieme a un AnimationController .

Qualsiasi widget, come la stella decorata mostrata in precedenza, può avere le sue proprietà animate. Ad esempio, il refactoring del codice in un StatefulWidget , poiché l'animazione è un cambiamento di stato e il passaggio di un AnimationController alla classe State consente di utilizzare il valore animato durante la creazione del widget.

 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 ), ), ); } ); } }

In questo caso, il valore viene utilizzato per variare la dimensione del widget. La funzione builder viene chiamata ogni volta che il valore animato cambia, facendo variare la dimensione della stella oltre 750 ms, creando una scala in effetti:

Animazione
Animazione della dimensione dell'icona

Utilizzo delle funzionalità native

Canali della piattaforma

Per fornire l'accesso alle API della piattaforma nativa su Android e iOS, un'applicazione Flutter può utilizzare i canali della piattaforma. Questi consentono al codice Flutter Dart di inviare messaggi all'applicazione iOS o Android di hosting. Molti dei plug-in open source disponibili sono creati utilizzando la messaggistica sui canali della piattaforma. Per imparare a lavorare con i canali della piattaforma, la documentazione di Flutter include un buon documento che dimostra l'accesso alle API della batteria nativa.

Conclusione

Anche in versione beta, Flutter offre un'ottima soluzione per la creazione di applicazioni multipiattaforma. Con i suoi strumenti eccellenti e la ricarica a caldo, offre un'esperienza di sviluppo molto piacevole. La ricchezza di pacchetti open source e l'eccellente documentazione rendono facile iniziare. Guardando al futuro, gli sviluppatori Flutter potranno scegliere come target Fuchsia oltre a iOS e Android. Considerando l'estensibilità dell'architettura del motore, non mi sorprenderebbe vedere Flutter atterrare anche su una varietà di altre piattaforme. Con una community in crescita, è un ottimo momento per entrare.

Prossimi passi

  • Installa Flutter
  • Tour linguistico delle freccette
  • Flutter Codelab
  • Corso Flutter Udacity
  • Codice sorgente dell'articolo