Validation de formulaires Flutter - Form, FormState et TextFormField
Introduction à la validation
Section titled “Introduction à la validation”Flutter propose le widget Form pour centraliser la gestion des formulaires. Il simplifie la validation, la réinitialisation et la sauvegarde.
Form et FormState
Section titled “Form et FormState”Voici un exemple complet d’utilisation du widget Form avec validation :
class LoginForm extends StatefulWidget { @override _LoginFormState createState() => _LoginFormState();}
class _LoginFormState extends State<LoginForm> { final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); final _passwordController = TextEditingController();
@override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( children: [ TextFormField( controller: _emailController, decoration: InputDecoration(labelText: 'Email'), validator: (value) { if (value == null || value.isEmpty) { return 'Email obligatoire'; } if (!value.contains('@')) { return 'Email invalide'; } return null; }, ), TextFormField( controller: _passwordController, decoration: InputDecoration(labelText: 'Mot de passe'), obscureText: true, validator: (value) { if (value == null || value.isEmpty) { return 'Mot de passe obligatoire'; } if (value.length < 6) { return 'Mot de passe trop court'; } return null; }, ), ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); // Traitement du formulaire print('Formulaire valide'); } }, child: Text('Connexion'), ), ], ), ); }}TextFormField
Section titled “TextFormField”TextFormField intègre directement les fonctionnalités de validation :
TextFormField( controller: fieldController, validator: (String? value) { if (value == null || value.isEmpty) { return 'Champ obligatoire'; } return null; }, onSaved: (String? value) { // Sauvegarder la valeur },)Validation personnalisée
Section titled “Validation personnalisée”Créez des fonctions de validation réutilisables :
String? validateEmail(String? value) { if (value == null || value.isEmpty) { return 'Email obligatoire'; }
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(value)) { return 'Format d\'email invalide'; }
return null;}
String? validatePassword(String? value) { if (value == null || value.isEmpty) { return 'Mot de passe obligatoire'; }
if (value.length < 8) { return 'Minimum 8 caractères'; }
if (!value.contains(RegExp(r'[A-Z]'))) { return 'Au moins une majuscule'; }
if (!value.contains(RegExp(r'[0-9]'))) { return 'Au moins un chiffre'; }
return null;}Validation en temps réel
Section titled “Validation en temps réel”Implémenter une validation progressive pour une meilleure UX :
class ProgressiveValidationForm extends StatefulWidget { @override _ProgressiveValidationFormState createState() => _ProgressiveValidationFormState();}
class _ProgressiveValidationFormState extends State<ProgressiveValidationForm> { final _formKey = GlobalKey<FormState>(); bool _autoValidate = false;
@override Widget build(BuildContext context) { return Form( key: _formKey, autovalidateMode: _autoValidate ? AutovalidateMode.onUserInteraction : AutovalidateMode.disabled, child: Column( children: [ TextFormField( decoration: InputDecoration(labelText: 'Email'), validator: validateEmail, ), ElevatedButton( onPressed: () { setState(() => _autoValidate = true); if (_formKey.currentState!.validate()) { // Traitement... } }, child: Text('Valider'), ), ], ), ); }}États de chargement
Section titled “États de chargement”Gérer les états de chargement lors de la soumission :
class AsyncFormExample extends StatefulWidget { @override _AsyncFormExampleState createState() => _AsyncFormExampleState();}
class _AsyncFormExampleState extends State<AsyncFormExample> { final _formKey = GlobalKey<FormState>(); bool _isLoading = false;
Future<void> _submitForm() async { if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try { // Simulation d'une requête réseau await Future.delayed(Duration(seconds: 2));
ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Formulaire envoyé avec succès')), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur: $e')), ); } finally { setState(() => _isLoading = false); } }
@override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( children: [ TextFormField( decoration: InputDecoration(labelText: 'Nom'), validator: (value) => value?.isEmpty == true ? 'Obligatoire' : null, ), SizedBox(height: 20), ElevatedButton( onPressed: _isLoading ? null : _submitForm, child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text('Envoyer'), ), ], ), ); }}Gestion des erreurs réseau
Section titled “Gestion des erreurs réseau”Implémenter une gestion robuste des erreurs :
class ErrorHandlingForm extends StatefulWidget { @override _ErrorHandlingFormState createState() => _ErrorHandlingFormState();}
class _ErrorHandlingFormState extends State<ErrorHandlingForm> { final _formKey = GlobalKey<FormState>(); String? _globalError;
Future<void> _submitForm() async { setState(() => _globalError = null);
if (!_formKey.currentState!.validate()) return;
try { // Soumission du formulaire await submitToServer(); } on NetworkException catch (e) { setState(() => _globalError = 'Erreur réseau: Vérifiez votre connexion'); } on ValidationException catch (e) { setState(() => _globalError = 'Données invalides: ${e.message}'); } catch (e) { setState(() => _globalError = 'Erreur inattendue: $e'); } }
@override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( children: [ if (_globalError != null) Container( padding: EdgeInsets.all(12), color: Colors.red[100], child: Text( _globalError!, style: TextStyle(color: Colors.red[800]), ), ), // Autres champs... ], ), ); }}Thématisation des formulaires
Section titled “Thématisation des formulaires”Personnaliser l’apparence des formulaires via le thème :
MaterialApp( theme: ThemeData( inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder(), filled: true, fillColor: Colors.grey[100], ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( minimumSize: Size(double.infinity, 48), ), ), ), home: MyFormScreen(),)