À propos de ce cours
Chapitre 8 — Les formulaires Flutter · Les autres champs de formulaire : liste, cases, boutons radio, date
• proposer un choix dans une liste avec
DropdownButtonFormField; • utiliser des boutons radio avec
RadioGroup, une case à cocher et un interrupteur ;• saisir une date avec
showDatePicker; • rendre obligatoire un champ non textuel avec
FormField<bool>. 1. Un formulaire, plusieurs types de champs

Aperçu reconstitué de l'écran (Material 3) : le code de la leçon produit cet affichage, aux couleurs du thème près.
2. La liste déroulante
String? _filiere;
DropdownButtonFormField<String>(
initialValue: _filiere, // anciennement « value » (avant Flutter 3.35)
decoration: const InputDecoration(labelText: 'Filière', prefixIcon: Icon(Icons.school)),
items: const [
DropdownMenuItem(value: 'DD', child: Text('Développement digital')),
DropdownMenuItem(value: 'ID', child: Text('Infrastructure digitale')),
DropdownMenuItem(value: 'GE', child: Text('Gestion des entreprises')),
],
onChanged: (v) => setState(() => _filiere = v),
validator: (v) => v == null ? 'Choisissez une filière' : null,
onSaved: (v) => _filiere = v,
)
3. Boutons radio, interrupteur, case à cocher
enum Niveau { technicien, technicienSpecialise }
Niveau _niveau = Niveau.technicienSpecialise;
bool _annonces = true;
// Boutons radio : le RadioGroup gère la valeur choisie (Flutter 3.35 et plus)
RadioGroup<Niveau>(
groupValue: _niveau,
onChanged: (v) => setState(() => _niveau = v!),
child: const Column(
children: [
RadioListTile(value: Niveau.technicienSpecialise, title: Text('Technicien spécialisé')),
RadioListTile(value: Niveau.technicien, title: Text('Technicien')),
],
),
),
// Interrupteur
SwitchListTile(
title: const Text('Recevoir les annonces par e-mail'),
value: _annonces,
onChanged: (v) => setState(() => _annonces = v),
),
Sur une version de Flutter antérieure à 3.35, RadioGroup n'existe pas : on place groupValue et onChanged
directement sur chaque RadioListTile. Ces deux propriétés sont aujourd'hui marquées obsolètes.
4. Un champ obligatoire qui n'est pas du texte
FormField<bool>(
initialValue: false,
validator: (v) => v == true ? null : 'Vous devez accepter le règlement',
builder: (field) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CheckboxListTile(
value: field.value,
onChanged: (v) => field.didChange(v), // prévient le formulaire du changement
title: const Text("J'accepte le règlement intérieur"),
controlAffinity: ListTileControlAffinity.leading,
),
if (field.hasError)
Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(field.errorText!,
style: TextStyle(color: Theme.of(context).colorScheme.error, fontSize: 12)),
),
],
),
),
FormField<T> transforme n'importe quel widget en champ de formulaire : il reçoit un validator, participe à
validate(), save() et reset(), et fournit au builder un objet field (valeur, erreur, didChange).
5. Une date
final _dateCtrl = TextEditingController();
DateTime? _naissance;
TextFormField(
controller: _dateCtrl,
readOnly: true, // pas de clavier : on ouvre le calendrier
decoration: const InputDecoration(labelText: 'Date de naissance', prefixIcon: Icon(Icons.calendar_today)),
validator: (_) => _naissance == null ? 'Choisissez une date' : null,
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: DateTime(2007),
firstDate: DateTime(1960),
lastDate: DateTime.now(),
);
if (date != null) {
setState(() {
_naissance = date;
_dateCtrl.text = '${date.day.toString().padLeft(2, '0')}/'
'${date.month.toString().padLeft(2, '0')}/${date.year}';
});
}
},
),
•
DropdownButtonFormField: items, initialValue, validator. • Boutons radio : un
RadioGroup autour de RadioListTile; interrupteur : SwitchListTile. • Case obligatoire :
FormField<bool> + CheckboxListTile + field.didChange. • Date : champ en lecture seule +
showDatePicker au toucher.🛠 Activité 5 — L'inscription à un module
Mission : Réalisez l'écran de la figure 1 : filière, niveau, date de naissance, annonces, acceptation du règlement, et un résumé après validation.
Questions pour avancer (répondez dans votre compte rendu, puis appliquez dans votre projet) :
- Pourquoi utiliser un type
enumpour le niveau plutôt que des chaînes « technicien » ? Que gagnez-vous si vous faites une faute de frappe ? - Le stagiaire doit avoir au moins 15 ans. Où vérifiez-vous cette règle ? Comment calculez-vous l'âge à partir de la date choisie ?
- La case « règlement » non cochée doit bloquer l'envoi avec un message en rouge. Qu'arrive-t-il si vous oubliez
field.didChange(v)? - Appuyez sur « Effacer » (
reset()) : quels champs reviennent à leur valeur de départ ? La date est-elle effacée ? Corrigez. - Ajoutez la filière « Gestion » qui fait apparaître une deuxième liste « Option » (comptabilité, commerce). Comment affichez-vous ce champ seulement dans ce cas ?
- Affichez un résumé dans une
AlertDialogaprès validation. Comment affichez-vous le niveau de façon lisible à partir de l'enum ?
Je vérifie / je rends :
- ☐ Le code et deux captures : formulaire avec erreurs, puis résumé.
← Validation des e-mails dans FlutterTP 8 : De la maquette Figma à l'application Flutter – inscription en 3 écrans →