Kodokon kodokon.com

Formulaires : TextField, contrôleurs et validation

Pilotez vos champs de saisie avec TextEditingController et validez toutes les entrées d'un coup grâce à Form et TextFormField.

9 min · 3 questions

Ouvrir cette leçon dans Kodokon

Un champ de saisie sans contrôleur est une boîte noire. TextEditingController vous donne la main : lire controller.text à tout moment, préremplir le champ, le vider après envoi, ou écouter chaque frappe avec addListener. Le contrôleur se crée dans un State, jamais dans build - sinon il serait recréé à chaque reconstruction.

DART
import 'package:flutter/material.dart';

class NicknameField extends StatefulWidget {
  const NicknameField({super.key});

  @override
  State<NicknameField> createState() =>
      _NicknameFieldState();
}

class _NicknameFieldState extends State<NicknameField> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: const InputDecoration(
        labelText: 'Pseudo',
        border: OutlineInputBorder(),
      ),
      onSubmitted: (value) => debugPrint(value),
    );
  }
}
Le contrôleur vit dans le State et meurt dans dispose().

Dès qu'un écran compte plusieurs champs à vérifier, adoptez le trio Form, GlobalKey<FormState> et TextFormField. Chaque champ décrit sa règle dans validator : la fonction renvoie un message d'erreur, ou null si tout va bien. Un appel à validate() sur la clé déclenche tous les validateurs d'un coup.

DART
import 'package:flutter/material.dart';

class SignUpForm extends StatefulWidget {
  const SignUpForm({super.key});

  @override
  State<SignUpForm> createState() => _SignUpFormState();
}

class _SignUpFormState extends State<SignUpForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  void _submit() {
    if (_formKey.currentState!.validate()) {
      debugPrint('Email : ${_emailController.text}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextFormField(
            controller: _emailController,
            keyboardType: TextInputType.emailAddress,
            decoration: const InputDecoration(
              labelText: 'E-mail',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Champ obligatoire';
              }
              if (!value.contains('@')) {
                return 'Adresse invalide';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          FilledButton(
            onPressed: _submit,
            child: const Text('Créer le compte'),
          ),
        ],
      ),
    );
  }
}
validate() déclenche tous les validators du formulaire.

Au clic sur le bouton, validate() exécute chaque validator : les messages renvoyés s'affichent en rouge sous les champs concernés, et la méthode renvoie false si au moins un champ est invalide. Votre logique d'envoi reste donc derrière un seul if - impossible de soumettre des données invalides.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. À quoi sert un TextEditingController ?
    • À dessiner la bordure du champ
    • À lire et modifier le texte du champ, et écouter ses changements
    • À valider automatiquement les adresses e-mail
    • À fermer le clavier
  2. Que doit renvoyer un validator quand la valeur est valide ?
    • true
    • Une chaîne vide
    • null
    • La valeur elle-même
  3. Pourquoi appeler _controller.dispose() dans la méthode dispose() du State ?
    • Pour vider le champ à l'écran
    • Pour libérer les ressources et éviter les fuites mémoire
    • Pour soumettre le formulaire