Nelle lezioni precedenti, abbiamo analizzato in dettaglio i possibili approcci per navigare tra le schermate di un'applicazione così da permettere all'utente di scoprire le funzionalità disponibili e i dati forniti.
Come tutte le applicazioni moderne a cui siamo abituati, un aspetto utile è lo scambio di informazioni tra due schermate. La condivisione dei dati è, infatti, molto importante durante la navigazione dell'utente in quanto gli permette di interagire con nuove informazioni a partire dalle prime. Ad esempio, quando l'utente interagisce con una lista di elementi e clicca su uno di questi, l'informazione relativa all'identificativo univoco dell'elemento viene inoltrata alla schermata successiva per estrarre l'entità dal DB e popolare la schermata di dettaglio.
In questa e nella prossima lezione vedremo nel dettaglio tre diversi approcci per condividere dati durante la navigazione attraverso esempi pratici ed intuitivi.
Scambio dati tra due schermate tramite MaterialRoutePage
Vediamo alcuni esempi pratici per lo scambio dati tra due schermate di un'applicazione contestualmente alla navigazione, sfruttando proprio i componenti Route
, Navigator
e MaterialRoutePage
con cui abbiamo familiarizzato nella lezione precedente.
Come di consueto, creiamo un nuovo progetto come illustrato nella lezione 6 di questa guida e cancelliamo tutto eccetto la classe MyApp
, definita come segue.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: /*set home*/,
}
}
Prima di proseguire con gli esempi, definiamo una classe di oggetti che utilizzeremo come dato da passare tra due schermate. In particolare, andremo a definire la classe Article
che rappresenta un articolo composto da due proprietà:
- un titolo;
- una descrizione.
Per farlo basterà definire la classe come segue.
class Article {
final String title;
final String description;
Article(this.title, this.description);
}
In particolare, abbiamo definito all'interno della classe un costruttore che accetta come parametri di ingresso il titolo e la descrizione.
Per semplicità abbiamo messo questa classe all'interno del file main.dart, ma è opportuno definire il modello di dominio all'interno di un apposito file .dart contenente le entità che rappresentano il dominio della nostra applicazione. L'organizzazione del codice sarà oggetto delle prossime lezioni.
Definiamo adesso due schermate:
- una che chiameremo
HomePage
, che mostrerà un semplice bottone e inoltrerà i dati alla seconda schermata; - una schermata di dettaglio,
ArticleDetailPage
, che mostrerà le informazioni (titolo e descrizione) dell'articolo selezionato dall'utente.
Vediamo come definire le due classi e partiamo dalla HomePage
.
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: RaisedButton(
onPressed: () {ì
},
child: Text('Open ArticleDetailPage'),
),
),
);
}
}
In questo modo, abbiamo creato un nuovo StatelessWidget
composto da:
- uno
Scaffold
; - un
AppBar
; - un
RaisedButton
posto al centro della schermata.
Quando l'utente cliccherà sul bottone, aprirà la pagina di dettaglio che può essere definita così.
class ArticleDetailPage extends StatelessWidget {
final Article article;
ArticleDetailPage({Key key, this.article}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(article.title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(article.description),
),
);
}
}
In particolare abbiamo:
- definito la proprietà
article
di tipoArticle
; - definito il costruttore per la classe
ArticleDetailPage
che può prendere in input un oggetto di tipoArticle
; - sovrascritto il metodo
build
affinché il titolo dell'AppBar
sia impostato alla proprietà title definita nell'oggettoarticle
, ed è stato definito un widgetText
contenente la descrizione dell'articolo.
Non resta che definire la callback
per la proprietà RaisedButton.onPressed
della HomePage
che:
- definirà un nuovo oggetto
Article
composto da un titolo e una descrizione; - definirà un nuovo widget
Route
usando ilMaterialPageRoute
widget visto nella precedente lezione; - invocherà il metodo
Navigator.push
per comunicare al framework di navigare verso la nuova rotta.
Riportiamo di seguito una possibile implementazione della callback
così definita.
onPressed: () {
Article article =
new Article('Article', 'This is a simple text for Article');
Route route = MaterialPageRoute(
builder: (context) => ArticleDetailPage(article: article),
);
Navigator.push(context, route);
},
Impostiamo la proprietà MaterialApp.home
della classe MyApp
alla classe HomePage
home: HomePage(),
ed eseguiamo l'applicazione per vedere i risultati delle nostre modifiche.
Come si vede in figura, cliccando sul bottone presente nella prima schermata, l'utente crea un nuovo Article
e inoltra l'oggetto alla rotta che sostituisce la schermata della HomePage
con la rotta modale contenente il widget ArticleDetailPage
, che mostra i dettagli dell'articolo.
Complichiamo leggermente lo scenario e immaginiamo di avere una lista di articoli da visualizzare e da cui possiamo sceglierne uno per visualizzare un dettaglio.
Per farlo, dovremo creare nella nostra app una nuova schermata in cui visualizzare una lista predefinita e popolata staticamente. Vediamo come con il seguente esempio.
ArticleListPage({Key key, @required this.articles}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ArticleDetailPage(article: articles[index]),
),
);
},
);
},
),
);
}
}
In questo caso abbiamo definito:
- un nuovo widget,
ArticleListPage
, avente la proprietàarticles
che definisce una lista di oggettiArticle
da mostrare all'utente; - definito il costruttore della classe che prende come input (obbligatorio) una lista di articoli;
- all'interno del metodo
ArticleListPage.build
unoScaffold
contenente l'AppBar
e comebody
una nuovaListView
costruita a partire dalla proprietàarticles
.
Abbiamo inoltre definito la proprietà ListTile.onTap
(di cui abbiamo discusso nella lezione 18) in modo che, cliccando su un elemento della lista, la callback
invochi il metodo Navigator.push
per rimandare l'utente alla pagina di dettaglio con le nuove informazioni passate al costruttore del widget ArticleDetailPage
.
Adesso non resta che creare la nostra lista statica e modificare la classe MyApp
come segue.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// . . .
home: ArticleListPage(
articles: List.generate(
20,
(i) =>
Article(
'Article $i',
'This is a simple text for Article $i',
),
),
),
);
}
}
In questo caso abbiamo impostato la proprietà home
con la classe ArticleListPage
e assegnato alla proprietà ArticleListPage.articles
una lista generata dinamicamente tramite il metodo List.generate
, che crea una lista di 20 articoli composti da un titolo e una descrizione.
Non resta che eseguire l'applicazione per vedere i risultati delle modifiche effettuate.
Come si può notare, quando l'utente tocca uno specifico elemento della lista, la callback
della proprietà onTap
viene invocata dal framework per ridirezionare l'utente alla pagina di dettaglio tramite il Navigator
. Una volta atterrato sulla pagina di dettaglio l'utente vede il titolo e la descrizione dell'articolo selezionato.
Questo semplice esempio non ne fa uso, ma è possibile estendere quanto fatto alle rotte nominali.
Il codice di questa lezione è disponibile su GitHub.