4.3Passage de données entre pages
4.3.1 – Passage de paramètres lors de la navigation
La méthode la plus simple pour passer des données à un écran est de les transmettre directement au constructeur du widget lors de la navigation.
📤 Passer des données
Quand vous créez un nouvel écran avec Navigator.push(), vous pouvez passer des données en les transmettant au constructeur du widget.
Vous créez le widget avec des paramètres dans son constructeur, puis vous passez ces paramètres lors de la navigation. C'est comme passer des arguments à une fonction.
📝 Syntaxe
Voici comment passer des données lors de la navigation :
// Navigation avec données
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EcranDetails(
nom: 'Produit A',
prix: 29.99,
),
),
);
// Widget qui reçoit les données
class EcranDetails extends StatelessWidget {
final String nom;
final double prix;
const EcranDetails({
super.key,
required this.nom,
required this.prix,
});
@override
Widget build(BuildContext context) {
return Scaffold(...);
}
}
🧪 Exemple : Passage de données simple
Voici un exemple complet où on passe le nom d'un produit à l'écran de détails :
Navigator.push pour passer des données à l'écran de détails.
import 'package:flutter/material.dart';
void main() {
runApp(const MonApp());
}
class MonApp extends StatelessWidget {
const MonApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const EcranListe(),
);
}
}
// Écran avec une liste de produits
class EcranListe extends StatelessWidget {
const EcranListe({super.key});
@override
Widget build(BuildContext context) {
final produits = [
{'nom': 'Produit A', 'prix': 29.99},
{'nom': 'Produit B', 'prix': 39.99},
{'nom': 'Produit C', 'prix': 49.99},
];
return Scaffold(
appBar: AppBar(
title: const Text('Liste des produits'),
),
body: ListView.builder(
itemCount: produits.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(produits[index]['nom'] as String),
subtitle: Text('${produits[index]['prix']} €'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EcranDetails(
nom: produits[index]['nom'] as String,
prix: produits[index]['prix'] as double,
),
),
);
},
);
},
),
);
}
}
// Écran de détails qui reçoit les données
class EcranDetails extends StatelessWidget {
final String nom;
final double prix;
const EcranDetails({
super.key,
required this.nom,
required this.prix,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(nom),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Nom : $nom',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 16),
Text(
'Prix : $prix €',
style: const TextStyle(fontSize: 18),
),
],
),
),
);
}
}
Dans cet exemple :
- L'écran liste affiche une liste de produits
- Quand vous cliquez sur un produit,
Navigator.push()est appelé avec les données du produit - L'écran de détails reçoit
nometprixdans son constructeur - Les données sont affichées dans l'écran de détails
Le mot-clé
required indique que le paramètre est obligatoire. Si vous oubliez de le passer, Dart vous avertira d'une erreur.
Vous pouvez passer plusieurs paramètres de différents types (String, int, double, bool, objets personnalisés, etc.) au constructeur du widget.
4.3.2 – Retour de données vers l'écran précédent
Parfois, vous voulez que l'écran suivant retourne une valeur à l'écran précédent. Par exemple, après avoir rempli un formulaire, vous voulez retourner le résultat à l'écran qui a ouvert le formulaire.
🔄 Retourner des données
Pour retourner des données, vous utilisez Navigator.pop(context, valeur) au lieu de Navigator.pop(context). L'écran précédent peut alors récupérer cette valeur en utilisant await avec Navigator.push().
📝 Syntaxe
Voici comment retourner des données :
// Dans l'écran qui ouvre le formulaire
final resultat = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EcranFormulaire(),
),
);
// Dans l'écran qui retourne les données
Navigator.pop(context, 'Valeur retournée');
🧪 Exemple : Formulaire qui retourne un résultat
Voici un exemple où un formulaire retourne le texte saisi à l'écran précédent :
Navigator.push et Navigator.pop pour retourner des données à l'écran précédent.
import 'package:flutter/material.dart';
void main() {
runApp(const MonApp());
}
class MonApp extends StatelessWidget {
const MonApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const EcranAccueil(),
);
}
}
// Écran d'accueil
class EcranAccueil extends StatefulWidget {
const EcranAccueil({super.key});
@override
State<EcranAccueil> createState() => _EcranAccueilState();
}
class _EcranAccueilState extends State<EcranAccueil> {
String? texteRecu;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Accueil'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
final resultat = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EcranSaisie(),
),
);
if (resultat != null) {
setState(() {
texteRecu = resultat as String;
});
}
},
child: const Text('Ouvrir le formulaire'),
),
if (texteRecu != null) ...[
const SizedBox(height: 20),
Text(
'Texte reçu : $texteRecu',
style: const TextStyle(fontSize: 18),
),
],
],
),
),
);
}
}
// Écran de saisie qui retourne le texte
class EcranSaisie extends StatefulWidget {
const EcranSaisie({super.key});
@override
State<EcranSaisie> createState() => _EcranSaisieState();
}
class _EcranSaisieState extends State<EcranSaisie> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Saisie'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: 'Saisissez quelque chose',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context, _controller.text);
},
child: const Text('Valider et retourner'),
),
],
),
),
);
}
}
Dans cet exemple :
- L'écran d'accueil ouvre l'écran de saisie avec
await Navigator.push() - L'écran de saisie retourne le texte saisi avec
Navigator.pop(context, _controller.text) - L'écran d'accueil reçoit le résultat dans la variable
resultat - Le texte reçu est affiché sur l'écran d'accueil
await attend que Navigator.push() se termine (c'est-à -dire que l'écran soit fermé) avant de continuer. La valeur retournée par Navigator.pop() est assignée à la variable resultat.
Si l'utilisateur ferme l'écran sans appeler
Navigator.pop() avec une valeur (par exemple avec le bouton retour), resultat sera null. C'est pourquoi on vérifie if (resultat != null) avant d'utiliser la valeur.
Navigator.pop() retourne un Object? (peut être n'importe quel type). On utilise as String pour indiquer à Dart que cette valeur est une chaîne de caractères.
4.3.3 – Gestion des types et null safety
Dart utilise le null safety, ce qui signifie que vous devez gérer explicitement les valeurs qui peuvent être null. C'est particulièrement important lors du passage de données entre écrans.
🛡️ Null safety
En Dart, une variable peut être nullable (peut être null) ou non-nullable (ne peut jamais être null). Vous devez déclarer explicitement si une variable peut être null en ajoutant ? après le type.
📝 Syntaxe
Voici les différentes façons de gérer les types et le null safety :
// Variable non-nullable (obligatoire)
final String nom;
// Variable nullable (optionnelle)
final String? nomOptionnel;
// Retourner une valeur nullable
Navigator.pop(context, valeur); // peut ĂŞtre null
// Récupérer une valeur nullable
final resultat = await Navigator.push(...); // peut ĂŞtre null
if (resultat != null) {
// Utiliser resultat en sécurité
}
🧪 Exemple : Gestion complète des types
Voici un exemple qui montre comment gérer différents types de données et le null safety :
Navigator.push et Navigator.pop pour retourner un Map contenant des données.
import 'package:flutter/material.dart';
void main() {
runApp(const MonApp());
}
class MonApp extends StatelessWidget {
const MonApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const EcranAccueil(),
);
}
}
// Écran d'accueil
class EcranAccueil extends StatefulWidget {
const EcranAccueil({super.key});
@override
State<EcranAccueil> createState() => _EcranAccueilState();
}
class _EcranAccueilState extends State<EcranAccueil> {
String? messageRecu;
int? nombreRecu;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Accueil'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
final resultat = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EcranFormulaire(),
),
);
// Vérifier le type et la nullité
if (resultat != null && resultat is Map) {
setState(() {
messageRecu = resultat['message'] as String?;
nombreRecu = resultat['nombre'] as int?;
});
}
},
child: const Text('Ouvrir le formulaire'),
),
if (messageRecu != null) ...[
const SizedBox(height: 20),
Text(
'Message : $messageRecu',
style: const TextStyle(fontSize: 18),
),
],
if (nombreRecu != null) ...[
const SizedBox(height: 10),
Text(
'Nombre : $nombreRecu',
style: const TextStyle(fontSize: 18),
),
],
],
),
),
);
}
}
// Écran de formulaire
class EcranFormulaire extends StatefulWidget {
const EcranFormulaire({super.key});
@override
State<EcranFormulaire> createState() => _EcranFormulaireState();
}
class _EcranFormulaireState extends State<EcranFormulaire> {
final _messageController = TextEditingController();
final _nombreController = TextEditingController();
@override
void dispose() {
_messageController.dispose();
_nombreController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Formulaire'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _messageController,
decoration: const InputDecoration(
labelText: 'Message',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: _nombreController,
decoration: const InputDecoration(
labelText: 'Nombre',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
// Retourner null (annulation)
Navigator.pop(context, null);
},
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () {
// Retourner un Map avec les données
final nombre = int.tryParse(_nombreController.text);
Navigator.pop(context, {
'message': _messageController.text.isEmpty
? null
: _messageController.text,
'nombre': nombre,
});
},
child: const Text('Valider'),
),
],
),
],
),
),
);
}
}
Dans cet exemple :
- Les variables
messageRecuetnombreRecusont déclarées comme nullable avecString?etint? - Le formulaire retourne un
Mapcontenant les données, ounullsi l'utilisateur annule - On vérifie que
resultatn'est pasnullet qu'il est de typeMapavant de l'utiliser - On utilise
int.tryParse()pour convertir le texte en nombre de manière sécurisée (retournenullsi la conversion échoue)
L'opérateur
is vérifie le type d'une variable. resultat is Map vérifie si resultat est de type Map.
int.tryParse() essaie de convertir une chaîne en nombre entier. Si la conversion réussit, elle retourne le nombre. Si elle échoue, elle retourne null au lieu de lever une erreur.
- Toujours vérifier
nullavant d'utiliser une valeur nullable - Utiliser
ispour vérifier le type avant de faire un cast - Utiliser
tryParse()pour les conversions qui peuvent échouer - Déclarer les paramètres comme
requireds'ils sont obligatoires