Skip to content

Validation de formulaires Flutter - Form, FormState et TextFormField

Flutter propose le widget Form pour centraliser la gestion des formulaires. Il simplifie la validation, la réinitialisation et la sauvegarde.

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 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
},
)

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;
}

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'),
),
],
),
);
}
}

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'),
),
],
),
);
}
}

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...
],
),
);
}
}

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(),
)