Nelle scorsa lezioni abbiamo mosso i primi passi nella definizione della navigazione e del routing di un applicazione Flutter utilizzando il Navigator
e rotte nominali.
In questa lezione vedremo come:
- impostare una rotta iniziale diversa da
\
; - gestione degli errori durante la navigazione;
- utilizzo della proprietà
onGenerateRoute
e della classePageRouteBuilder
per la definizione di una transizione custom.
Impostare una rotta iniziale
Non necessariamente la home dell'applicazione deve essere rappresentata dalla rotta /
. Infatti, la rotta che permette al framework di identificare la prima schermata può essere chiamata in un modo qualsiasi, come ad esempio /home
. In questo caso, si dovrà impostare la proprietà MaterialApp.home
alla schermata desiderata.
Vediamo un esempio.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
routes: noSlashRouting(),
);
}
Map<String, WidgetBuilder> noSlashRouting(){
return {
'/home': (context) => HomePageV2(),
'/newpage': (context) => NewPageV2(),
};
}
}
Eseguendo l'applicazione, il risultato visivo e l'esperienza utente resteranno invariati rispetto ai risultati precedentemente ottenuti.
Un'altro aspetto interessante è la possibilità di utilizzare insieme alla proprietà route la proprietà intialRoute
di MaterialApp
, che permette di impostare una prima schermata differente da quella definita dalla rotta /
.
Per complicare lo scenario, aggiungiamo una terza schermata che conterrà a sua volta un RaisedButton
e impostiamo la proprietà onPressed
del bottone in modo che punti alla HomePage
.
class ThirdPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ThirdPage"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushNamed(context, '/');
},
child: Text('Go to HomePage!'),
),
),
);
}
}
A questo punto basterà aggiornare la classe MyApp
come segue.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/thirdpage',
routes: threeRouting(),
}
Map<String, WidgetBuilder> threeRouting(){
return {
'/': (context) => HomePage(),
'/newpage': (context) => NewPage(),
'/thirdpage': (context) => ThirdPage(),
};
}
}
In questo codice d'esempio abbiamo aggiornato le rotte della nostra applicazione aggiungendo una nuova rotta relativa alla nuova schermata ThirdPage
e abbiamo impostato la rotta iniziale dell'app proprio a questo elemento.
Eseguiamo quindi l'applicazione per ottenere il risultato desiderato.
Gestione degli errori di navigazione
Un'altra proprietà utile offerta dal widget MaterialApp
è chiamata onUnknownRoute
, che tramite una callback
permette di gestire possibili errori durante la generazione di una rotta, fatta eccezione per la proprietà MaterialApp.initialRoute
.
Generalmente, è consigliabile che questa callback
generi una rotta che descriva una pagina di not found per comunicare all'utente che è avvenuto un errore.
Vediamo con un semplice esempio come usare questa proprietà.
Creiamo prima di tutto un nuovo widget che rappresenterà la schermata di not found e chiameremo NotFoundPage
.
class NotFoundPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("PageNotFound"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back to HomePage!'),
),
),
);
}
}
Definita la classe, aggiorniamo la nostra MyApp
al fine di integrare la proprietà MaterialApp.onUnknownRoute
.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: mainRouting(),
onUnknownRoute: (RouteSettings setting) {
String unknownRoute = setting.name ;
return new MaterialPageRoute(
builder: (context) => NotFoundPage()
);
}
);
}
}
Modifichiamo infine la proprietà onPressed
affinchè il metodo Navigator.pushNamed
punti ad una rotta inesistente per generare un errore e mostrare la schermata NotFoundPage
.
onPressed: () {
Navigator.pushNamed(context, '/newp');
},
Eseguiamo l'applicazione per vedere i risultati delle modifiche effettuate.
In questo caso, il framework non ha trovato una rotta col nome /new
tra quelle disponibili e, invece di generare una schermata di errore, ha rimandato l'utente sulla pagina designata al fine di evidenziare un problema durante la navigazione.
Utilizzo della proprietà onGenerateRoute e della classe PageRouteBuilder
Vediamo infine come utilizzare la proprietà onGenerateRoute
per definire rotte nominali e integrare nella nostra applicazione delle transizioni personalizzate tra le schermate.
Come anticipato all'inizio di questa lezione, la classe MaterialPageRoute
definisce una rotta modale con una transizione dipendente dall'OS e per farlo estende la classe PageRoute
. Analogamente, se vogliamo definire una nostra transizione diversa da quella dei default offerta dagli OS, dovremo creare un nuovo widget che estende la classe PageRouteBuilder
(che estende PageRoute
) e definire le proprietà:
pageBuilder
per la creazione della pagina;transitionsBuilder
per la definizione della nostra transizione personalizzata.
Vediamo come farlo con un semplice esempio in cui l'obiettivo è creare una transizione che faccia entrare la nuova schermata da sinistra verso destra.
class SlideRightRoute extends PageRouteBuilder {
final Widget widget;
SlideRightRoute({this.widget})
: super(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return widget;
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return new SlideTransition(
position: new Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
);
}
In particolare, tramite la proprietà pageBuilder
definiamo il widget da mostrare, che viene passato come parametro del costruttore SlideRightRoute
. Invece, con la proprietà transitionsBuilder
, abbiamo definito a tutti gli effetti la nostra animazione personalizzata.
Definito il nostro nuovo PageRouteBuilder
personalizzato non resta che modificare la classe MyApp
per aggiungere la proprietà onGenerateRoute
.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/':
return SlideRightRoute(widget: HomePage());
break;
case '/newpage':
return SlideRightRoute(widget: NewPage());
break;
default:
return SlideRightRoute(widget: NotFoundPage());
}
},
);
}
In questo caso abbiamo definito uno switch
case in cui, dato il nome della rotta ottenuta dal RouteSettings
, tramite la proprietà name restituiamo lo SlideRightRoute
che prenderà in ingresso il widget da mostrare. Nel caso in cui il nome della rotta non fosse presente tra quelli definiti, lo switch
case restituirà la rotta di default, che in questo caso è stata impostata a NotFoundPage
ma può essere anche la HomePage della nostra app.
Eseguiamo quindi l'applicazione per vedere come si comporterà questa volta il framework durante la visualizzazione della nuova schermata.
Come si può notare, per entrambe le piattaforme è stata definita la medesima animazione per la transizione dalla HomePage
alla NewPage
.
È importante tenere a mente che, se decidiamo di utilizzare delle animazioni personalizzate per le transizioni tra schermate, queste andranno a sostituire del tutto e sempre le transazioni di default del sistema operativo (OS).
Il codice di questa lezione è disponibile su GitHub.