Sviluppo Web e desktop reattivo con Flutter

Pubblicato: 2022-03-10
Riassunto veloce ↬ Flutter ha già fatto un bel colpo sulla scena dello sviluppo mobile. Ora sta assumendo anche dispositivi più grandi. Ecco cosa devi sapere per essere pronto ad affrontare il compito di sviluppare app Web e desktop utilizzando questo meraviglioso framework multipiattaforma.

Questo tutorial non è un'introduzione a Flutter stesso. Ci sono molti articoli, video e diversi libri disponibili online con semplici introduzioni che ti aiuteranno ad apprendere le basi di Flutter. Invece, tratteremo i seguenti due obiettivi:

  1. Lo stato attuale dello sviluppo non mobile di Flutter e come eseguire il codice Flutter nel browser, su un computer desktop o laptop;
  2. Come creare app reattive utilizzando Flutter, in modo da poterne vedere la potenza, soprattutto come framework Web, a schermo intero, per finire con una nota sul routing basato sull'URL.

Entriamo!

Cos'è Flutter, perché è importante, in cosa si è evoluto, dove sta andando

Flutter è l'ultimo framework di sviluppo di app di Google. Google prevede che sia onnicomprensivo: consentirà l'esecuzione dello stesso codice su smartphone di tutte le marche, tablet e computer desktop e laptop come app native o pagine Web.

È un progetto molto ambizioso, ma fino ad ora Google ha avuto un successo incredibile, in particolare in due aspetti: nella creazione di un framework veramente indipendente dalla piattaforma per app native Android e iOS che funzioni alla grande ed è completamente pronto per l'uso in produzione, e nella creazione di un fronte impressionante -end framework web in grado di condividere il 100% del codice con un'app Flutter compatibile.

Nella prossima sezione, vedremo cosa rende compatibile l'app e qual è lo stato dello sviluppo di Flutter non mobile al momento.

Altro dopo il salto! Continua a leggere sotto ↓

Sviluppo non mobile con Flutter

Lo sviluppo non mobile con Flutter è stato pubblicizzato per la prima volta in modo significativo al Google I/O 2019. Questa sezione spiega come farlo funzionare e quando funziona.

Come abilitare lo sviluppo Web e desktop

Per abilitare lo sviluppo web, devi prima essere sul canale beta di Flutter. Ci sono due modi per arrivare a quel punto:

  • Installa Flutter direttamente sul canale beta scaricando l'ultima versione beta appropriata dall'archivio SDK.
  • Se hai già installato Flutter, passa al canale beta con $ flutter channel beta , quindi esegui il passaggio stesso aggiornando la tua versione di Flutter (che in realtà è un git pull nella cartella di installazione di Flutter) con $ flutter upgrade .

Successivamente, puoi eseguire questo:

 $ flutter config --enable-web

Il supporto desktop è molto più sperimentale, soprattutto a causa della mancanza di strumenti per Linux e Windows, rendendo lo sviluppo di plugin particolarmente una seccatura, e per il fatto che le API utilizzate per esso sono intese per un uso proof-of-concept e non per produzione. Questo è diverso dallo sviluppo Web, che utilizza il collaudato compilatore dart2js per le build di rilascio, che non sono nemmeno supportati per le app desktop native di Windows e Linux.

Nota : il supporto per macOS è leggermente migliore del supporto per Windows e Linux, ma non è ancora buono come il supporto per il Web e non è così buono come il supporto completo per piattaforme mobili.

Per abilitare il supporto per lo sviluppo desktop, devi passare al canale di rilascio master seguendo gli stessi passaggi descritti in precedenza per il canale beta . Quindi, esegui quanto segue sostituendo <OS_NAME> con linux , windows o macos :

 $ flutter config --enable-<OS_NAME>-desktop

A questo punto, se riscontri problemi con uno dei seguenti passaggi che descriverò perché lo strumento Flutter non sta facendo ciò che sto dicendo che dovrebbe fare, alcuni passaggi comuni per la risoluzione dei problemi sono questi:

  • Esegui il flutter doctor per verificare la presenza di problemi. Un effetto collaterale di questo comando Flutter è che dovrebbe scaricare tutti gli strumenti necessari che non ha.
  • Esegui flutter upgrade .
  • Spegnilo e riaccendilo. La vecchia risposta del supporto tecnico di livello 1 del riavvio del computer potrebbe essere proprio ciò di cui hai bisogno per poter godere di tutte le ricchezze di Flutter.

Esecuzione e creazione di app Web Flutter

Il supporto Web Flutter non è affatto male e questo si riflette nella facilità di sviluppo per il Web.

Esecuzione di questo...

 $ flutter devices

... dovrebbe mostrare subito una voce per qualcosa del genere:

 Web Server • web-server • web-javascript • Flutter Tools

Inoltre, l'esecuzione del browser Chrome dovrebbe far sì che anche Flutter mostri una voce per esso. L'esecuzione di flutter run su un progetto Flutter compatibile (ne parleremo più avanti) quando l'unico "dispositivo connesso" visualizzato è il server Web farà sì che Flutter avvii un server Web su localhost:<RANDOM_PORT> , che ti consentirà di accedere al tuo Flutter app web da qualsiasi browser.

Se hai installato Chrome ma non viene visualizzato, devi impostare la variabile di ambiente CHROME_EXECUTABLE sul percorso del file eseguibile di Chrome.

Esecuzione e creazione di app Flutter desktop

Dopo aver abilitato il supporto desktop Flutter, è possibile eseguire un'app Flutter in modo nativo sulla workstation di sviluppo con flutter run -d <OS_NAME> , sostituendo <OS_NAME> con lo stesso valore utilizzato durante l'abilitazione del supporto desktop. Puoi anche creare file binari nella directory build con flutter build <OS_NAME> .

Prima di poter fare qualsiasi cosa, però, devi avere una directory che contenga ciò che Flutter deve creare per la tua piattaforma. Questo verrà creato automaticamente quando crei un nuovo progetto, ma dovrai crearlo per un progetto esistente con flutter create . . Inoltre, le API Linux e Windows sono instabili, quindi potresti doverle rigenerare per quelle piattaforme se l'app smette di funzionare dopo un aggiornamento Flutter.

Quando è compatibile un'app?

Cosa ho sempre voluto dire quando ho menzionato che un'app Flutter deve essere un "progetto compatibile" affinché funzioni su desktop o sul Web? In parole povere, intendo dire che non deve utilizzare alcun plug-in che non abbia un'implementazione specifica della piattaforma per la piattaforma su cui stai cercando di costruire.

Per rendere questo punto assolutamente chiaro a tutti ed evitare malintesi, tieni presente che un plug-in Flutter è un particolare pacchetto Flutter che contiene codice specifico della piattaforma necessario per fornire le sue funzionalità.

Ad esempio, puoi utilizzare il pacchetto url_launcher sviluppato da Google quanto vuoi (e potresti volerlo, dato che il Web è basato su collegamenti ipertestuali).

Un esempio di un pacchetto sviluppato da Google il cui utilizzo precluderebbe lo sviluppo web è path_provider , che viene utilizzato per ottenere il percorso di archiviazione locale in cui salvare i file. Questo è un esempio di un pacchetto che, per inciso, non è di alcuna utilità per un'app Web, quindi non poterlo utilizzare non è davvero un peccato, tranne per il fatto che è necessario modificare il codice per per funzionare sul web se lo stai usando.

Ad esempio, puoi utilizzare il pacchetto shared_preferences, che si basa su HTML localStorage sul Web.

Avvertenze simili sono valide per quanto riguarda le piattaforme desktop: pochissimi plug-in sono compatibili con le piattaforme desktop e, poiché si tratta di un tema ricorrente, è necessario fare molto più lavoro su questo lato desktop di quanto sia realmente necessario su Flutter per il web.

Creazione di layout reattivi in ​​Flutter

A causa di ciò che ho descritto sopra e per semplicità, presumerò per il resto di questo post che la tua piattaforma di destinazione sia il Web, ma i concetti di base si applicano anche allo sviluppo desktop.

Sostenere il web ha vantaggi e responsabilità. Essere praticamente costretti a supportare dimensioni dello schermo diverse potrebbe sembrare uno svantaggio, ma considera che l'esecuzione dell'app nei browser Web ti consente di vedere molto facilmente come apparirà la tua app su schermi di dimensioni e proporzioni diverse, senza dover eseguire separatamente emulatori di dispositivi mobili.

Ora parliamo di codice. Come puoi rendere reattiva la tua app?

Ci sono due prospettive da cui questa analisi viene fatta:

  1. "Quali widget sto usando o posso usare che possono o dovrebbero adattarsi a schermi di diverse dimensioni?"
  2. "Come posso ottenere informazioni sulle dimensioni dello schermo e come posso utilizzarle durante la scrittura del codice dell'interfaccia utente?"

Risponderemo alla prima domanda più avanti. Parliamo prima di quest'ultimo, perché può essere affrontato molto facilmente ed è al centro della questione. Ci sono due modi per farlo:

  1. Un modo è prendere le informazioni da MediaQueryData della radice MediaQuery InheritedWidget , che deve esistere nell'albero dei widget affinché un'app Flutter funzioni (fa parte di MaterialApp/WidgetsApp/CupertinoApp ), che puoi ottenere, proprio come qualsiasi altro InheritedWidget , con MediaQuery.of(context) , che ha una proprietà size , che è di tipo Size , e che quindi ha due proprietà width e height del tipo double .
  2. L'altro modo è utilizzare un LayoutBuilder , che è un widget builder (proprio come uno StreamBuilder o un FutureBuilder ) che passa alla funzione builder (insieme a context ) un oggetto BoxConstraints che ha minHeight , maxHeight , minWidth e maxWidth .

Ecco un esempio di DartPad che utilizza MediaQuery per ottenere i vincoli, il cui codice è il seguente:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${MediaQuery.of(context).size.width}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${MediaQuery.of(context).size.height}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ); }

Ed eccone uno che utilizza LayoutBuilder per la stessa cosa:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( "Width: ${constraints.maxWidth}", style: Theme.of(context).textTheme.headline4 ), Text( "Height: ${constraints.maxHeight}", style: Theme.of(context).textTheme.headline4 ) ] ) ) ) ); }

Ora, pensiamo a quali widget possono adattarsi ai vincoli.

Prima di tutto, pensiamo ai diversi modi di disporre più widget in base alle dimensioni dello schermo.

Il widget che si adatta più facilmente è il GridView . In effetti, un GridView creato utilizzando il costruttore GridView.extent non ha nemmeno bisogno del tuo coinvolgimento per essere reso reattivo, come puoi vedere in questo esempio molto semplice:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); } import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List elements = [ "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit" ]; @override Widget build(context) => Scaffold( body: GridView.extent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList() ) ); }

Puoi ospitare contenuti di dimensioni diverse modificando maxCrossAxisExtent .

Quell'esempio serviva principalmente allo scopo di mostrare l'esistenza del costruttore GridView.extent GridView , ma un modo molto più intelligente per farlo sarebbe usare un GridView.builder con un SliverGridDelegateWithMaxCrossAxisExtent , in questo caso dove i widget da mostrare nella griglia vengono creati dinamicamente da un'altra struttura dati, come puoi vedere in questo esempio:

 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: MyHomePage() ); } class MyHomePage extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => Scaffold( body: GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ) ); }

Un esempio di GridView che si adatta a schermi diversi è la mia pagina di destinazione personale, che è un'app Web Flutter molto semplice composta da un GridView con un gruppo di Cards , proprio come il codice di esempio precedente, tranne per il fatto che le Cards sono un po' più complesse e più grandi .

Una modifica molto semplice che potrebbe essere apportata alle app progettate per i telefoni sarebbe quella di sostituire un Drawer con un menu permanente sulla sinistra quando c'è spazio.

Ad esempio, potremmo avere una ListView di widget, come la seguente, che viene utilizzata per la navigazione:

 class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); }

Su uno smartphone, un luogo comune da utilizzare che sarebbe all'interno di un Drawer (noto anche come menu di hamburger).

Alternative a questo sarebbero BottomNavigationBar o TabBar , in combinazione con TabBarView , ma con entrambi dovremmo apportare più modifiche di quelle richieste con il drawer, quindi rimarremo con il drawer.

Per mostrare solo il Drawer contenente il Menu che abbiamo visto in precedenza su schermi più piccoli, dovresti scrivere un codice simile al seguente snippet, controllando la larghezza usando MediaQuery.of(context) e passando un oggetto Drawer allo Scaffold solo se è inferiore a un valore di larghezza che riteniamo appropriato per la nostra app:

 Scaffold( appBar: AppBar(/* ... \*/), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: /* ... \*/ )

Ora, pensiamo al body Scaffold . Come contenuto principale di esempio della nostra app, utilizzeremo GridView che abbiamo creato in precedenza, che conserviamo in un widget separato chiamato Content per evitare confusione:

 class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); } class Content extends StatelessWidget { final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }

Su schermi più grandi, il corpo stesso può essere una Row che mostra due widget: il Menu , che è limitato a una larghezza fissa, e il Content che riempie il resto dello schermo.

Su schermi più piccoli, l'intero body sarebbe il Content .

Avvolgeremo tutto in un widget SafeArea e Center perché a volte i widget dell'app Web Flutter, specialmente quando si utilizzano Row e Column s, finiscono al di fuori dell'area dello schermo visibile e ciò viene risolto con SafeArea e/o Center .

Ciò significa che il body Scaffold sarà il seguente:

 SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) )

Ecco tutto questo messo insieme:

(Grande anteprima)
 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( home: HomePage() ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }

Questa è la maggior parte delle cose di cui avrai bisogno come introduzione generale all'interfaccia utente reattiva in Flutter. Gran parte della sua applicazione dipenderà dall'interfaccia utente specifica della tua app ed è difficile individuare esattamente cosa puoi fare per rendere la tua app reattiva e puoi adottare molti approcci a seconda delle tue preferenze. Ora, però, vediamo come possiamo trasformare un esempio più completo in un'app reattiva, pensando agli elementi comuni dell'app e ai flussi dell'interfaccia utente.

Mettendolo nel contesto: rendere un'app reattiva

Finora, abbiamo solo uno schermo. Espandilo in un'app a due schermi con navigazione basata su URL funzionante!

Creazione di una pagina di accesso reattiva

È probabile che la tua app abbia una pagina di accesso. Come possiamo renderlo reattivo?

Le schermate di accesso sui dispositivi mobili sono generalmente abbastanza simili tra loro. Lo spazio a disposizione non è molto; di solito è solo una Column con un po' di Padding attorno ai suoi widget e contiene TextField per digitare un nome utente e una password e un pulsante per accedere. Quindi, uno standard piuttosto carino (sebbene non funzionante, poiché ciò richiederebbe, tra le altre cose , un TextEditingController per ogni TextField ) pagina di accesso per un'app mobile potrebbe essere la seguente:

 Scaffold( body: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () {} ) ] ), ), )

Sembra a posto su un dispositivo mobile, ma quei TextField molto ampi iniziano a sembrare stridenti su un tablet, per non parlare di uno schermo più grande. Tuttavia, non possiamo semplicemente decidere una larghezza fissa perché i telefoni hanno dimensioni dello schermo diverse e dovremmo mantenere un certo grado di flessibilità.

Ad esempio, attraverso la sperimentazione, potremmo scoprire che la larghezza massima dovrebbe essere 500. Bene, impostiamo i constraints di Container su 500 (ho usato un Container invece di Padding nell'esempio precedente perché sapevo dove stavo andando con questo ) e siamo a posto, giusto? Non proprio, perché ciò farebbe rimanere i widget di accesso sul lato sinistro dello schermo, il che potrebbe essere anche peggio di allungare tutto. Quindi, avvolgiamo un widget Center , in questo modo:

 Center( child: Container( constraints: BoxConstraints(maxWidth: 500), padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), child: Column(/* ... \*/) ) )

Sembra già a posto e non abbiamo nemmeno dovuto usare né un LayoutBuilderMediaQuery.of(context).size . Facciamo un ulteriore passo avanti per rendere questo aspetto molto buono, però. Sembrerebbe migliore, a mio avviso, se la parte in primo piano fosse in qualche modo separata dallo sfondo. Possiamo ottenerlo dando un colore di sfondo a ciò che c'è dietro il Container con i widget di input e mantenendo il Container in primo piano bianco. Per renderlo un po' migliore, impediamo al Container di allungarsi verso la parte superiore e inferiore dello schermo su dispositivi di grandi dimensioni, diamogli angoli arrotondati e diamogli una bella transizione animata tra i due layout.

Tutto ciò ora richiede un LayoutBuilder e un Container esterno sia per impostare un colore di sfondo che per aggiungere riempimento intorno al Container e non solo sui lati solo su schermi più grandi. Inoltre, per rendere animata la modifica della quantità di riempimento, dobbiamo solo trasformare quel Container esterno in un AnimatedContainer , che richiede una duration per l'animazione, che imposteremo su mezzo secondo, che è Duration(milliseconds: 500) in codice.

Ecco l'esempio di una pagina di accesso reattiva:

(Grande anteprima)
 class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : EdgeInsets.all(30.0), child: Center( child: Container( padding: EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomePage() ) ); } ) ] ), ), ) ); } ) ); }

Come puoi vedere, ho anche modificato RaisedButton di onPressed in un callback che ci porta a una schermata denominata HomePage (che potrebbe essere, ad esempio, la vista che abbiamo creato in precedenza con un GridView e un menu o un drawer). Ora, però, quella parte di navigazione è ciò su cui ci concentreremo.

Percorsi con nome: rendere la navigazione della tua app più simile a una vera e propria app Web

Una cosa comune per le app Web è la possibilità di cambiare le schermate in base all'URL. Ad esempio, andare su https://appurl/login dovrebbe darti qualcosa di diverso da https://appurl/somethingelse . Flutter, infatti, supporta percorsi denominati , che hanno due scopi:

  1. In un'app web, hanno esattamente quella caratteristica che ho menzionato nella frase precedente.
  2. In qualsiasi app, ti consentono di predefinire percorsi per la tua app e dare loro dei nomi, quindi essere in grado di navigare verso di loro semplicemente specificando il loro nome.

Per fare ciò, dobbiamo cambiare il costruttore MaterialApp in uno simile al seguente:

 MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } );

E quindi possiamo passare a un percorso diverso utilizzando Navigator.pushNamed(context, routeName) e Navigator.pushReplacementNamed(context, routeName) , invece di Navigator.push(context, route) e Navigator.pushReplacement(context, route) .

Ecco quello applicato all'ipotetica app che abbiamo creato nel resto di questo articolo. Non puoi davvero vedere percorsi con nome in azione in DartPad, quindi dovresti provarlo sulla tua macchina con flutter run o controllare l'esempio in azione:

(Grande anteprima)
 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(context) => MaterialApp( initialRoute: "/login", routes: { "/login": (context) => LoginPage(), "/home": (context) => HomePage() } ); } class LoginPage extends StatelessWidget { @override Widget build(context) => Scaffold( body: LayoutBuilder( builder: (context, constraints) { return AnimatedContainer( duration: Duration(milliseconds: 500), color: Colors.lightGreen[200], padding: constraints.maxWidth < 500 ? EdgeInsets.zero : const EdgeInsets.all(30.0), child: Center( child: Container( padding: const EdgeInsets.symmetric( vertical: 30.0, horizontal: 25.0 ), constraints: BoxConstraints( maxWidth: 500, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text("Welcome to the app, please log in"), TextField( decoration: InputDecoration( labelText: "username" ) ), TextField( obscureText: true, decoration: InputDecoration( labelText: "password" ) ), RaisedButton( color: Colors.blue, child: Text("Log in", style: TextStyle(color: Colors.white)), onPressed: () { Navigator.pushReplacementNamed( context, "/home" ); } ) ] ), ), ) ); } ) ); } class HomePage extends StatelessWidget { @override Widget build(context) => Scaffold( appBar: AppBar(title: Text("test")), drawer: MediaQuery.of(context).size.width < 500 ? Drawer( child: Menu(), ) : null, body: SafeArea( child:Center( child: MediaQuery.of(context).size.width < 500 ? Content() : Row( children: [ Container( width: 200.0, child: Menu() ), Container( width: MediaQuery.of(context).size.width-200.0, child: Content() ) ] ) ) ) ); } class Menu extends StatelessWidget { @override Widget build(context) => ListView( children: [ FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_one), title: Text("First Link"), ) ), FlatButton( onPressed: () {}, child: ListTile( leading: Icon(Icons.looks_two), title: Text("Second Link"), ) ), FlatButton( onPressed: () {Navigator.pushReplacementNamed( context, "/login");}, child: ListTile( leading: Icon(Icons.exit_to_app), title: Text("Log Out"), ) ) ] ); } class Content extends StatelessWidget { final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"]; @override Widget build(context) => GridView.builder( itemCount: elements.length, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 130.0, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, ), itemBuilder: (context, i) => Card( child: Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text(elements[i]) ) ) ) ); }

Avanti con la tua avventura svolazzante

Questo dovrebbe darti un'idea di cosa puoi fare con Flutter su schermi più grandi, in particolare sul web. È un framework adorabile, molto facile da usare e il suo supporto multipiattaforma estremo rende solo più essenziale l'apprendimento e l'inizio dell'utilizzo. Quindi, vai avanti e inizia a fidarti anche di Flutter per le app Web!

Ulteriori risorse

  • "shell per desktop", GitHub
    Lo stato attuale e sempre aggiornato di Flutter su desktop
  • "Supporto desktop per Flutter", Flutter
    Informazioni sulle piattaforme desktop completamente supportate
  • "Supporto Web per Flutter", Flutter
    Informazioni su Flutter per il web
  • "Tutti i campioni", Flutter
    Un elenco curato di campioni e app Flutter