flutter-formularios-validacion-globalkey

Validar formularios en Flutter con Form y GlobalKey

  • 4 min

Un formulario es un conjunto de campos que recoge y valida datos introducidos por el usuario.

En el artículo anterior aprendimos a usar TextField para que el usuario escriba. Pero esos datos pueden estar incompletos o tener un formato incorrecto.

Escriben correos sin @, dejan campos vacíos o ponen letras en el campo de “Edad”. Si enviamos esos datos “sucios” a nuestro servidor, todo fallará.

Para detectarlo antes del envío, Flutter proporciona el widget Form y sus validadores. La validación del cliente mejora la experiencia, pero el servidor también debe validar siempre los datos.

El widget Form

El Form es un contenedor invisible. Por sí solo no pinta nada, pero sirve para agrupar varios inputs y validarlos todos a la vez.

Para usarlo, necesitamos dos ingredientes especiales:

  1. Sustituir nuestros TextField por TextFormField.
  2. Crear una Llave Global (GlobalKey) para controlar el formulario desde fuera.

TextField y TextFormField

Hasta ahora usábamos TextField. Dentro de un formulario, usaremos su hermano mayor: TextFormField.

Son visualmente idénticos, pero el TextFormField tiene una propiedad extra muy importante: validator.

La lógica del validador

La propiedad validator es una función que recibe el texto que ha escrito el usuario y debe devolver:

  • null: Si el dato es CORRECTO.
  • "Texto de error": Si el dato es INCORRECTO. Flutter mostrará este texto en rojo debajo del input.
TextFormField(
  decoration: InputDecoration(labelText: "Nombre"),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Por favor, escribe algo'; // ❌ Error
    }
    return null; // ✅ Todo bien
  },
)
Copied!

Controlar el formulario con GlobalKey<FormState>

Aquí viene la parte técnica. ¿Cómo sabe el botón “Enviar” que tiene que revisar los inputs que están más arriba en el código?

Necesitamos un “mando a distancia” que conecte el botón con el formulario. Ese mando es la GlobalKey.

class MiFormulario extends StatefulWidget {
  @override
  _MiFormularioState createState() => _MiFormularioState();
}

class _MiFormularioState extends State<MiFormulario> {
  // 1. Creamos la llave única para este formulario
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey, // 2. Asignamos la llave al widget Form
      child: Column(
        children: [
          TextFormField(
            validator: (value) {
              if (value!.isEmpty) return 'Campo obligatorio';
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              // 3. Usamos la llave para validar
              if (_formKey.currentState!.validate()) {
                // Si devuelve true, todo está OK
                print("Procesando datos...");
              }
            },
            child: Text("Enviar"),
          )
        ],
      ),
    );
  }
}
Copied!

¿Qué hace .validate()?

Cuando llamas a _formKey.currentState!.validate(), Flutter recorre todos los TextFormField que hay dentro de ese Form, ejecuta sus funciones validator una por una y:

  1. Si alguno falla, pinta el error en rojo y devuelve false.
  2. Si todos devuelven null, devuelve true.

Validación de formato

Validar que no esté vacío es fácil. ¿Pero cómo validamos un email real? Aquí entran las Expresiones Regulares (Regex).

Dart tiene la clase RegExp para ayudarnos.

TextFormField(
  decoration: InputDecoration(labelText: "Email"),
  keyboardType: TextInputType.emailAddress,
  validator: (value) {
    // 1. Validamos que no esté vacío
    if (value == null || value.isEmpty) return 'Requerido';

    // 2. Validamos el formato con Regex
    // Comprobación básica de estructura, no de que la cuenta exista
    String pattern = r'^[^\s@]+@[^\s@]+\.[^\s@]+$';
    RegExp regex = RegExp(pattern);

    if (!regex.hasMatch(value)) {
      return 'Introduce un email válido';
    }

    return null;
  },
)
Copied!

El prefijo r antes del string (r'...') indica que es un “Raw String”. Es necesario para escribir Regex en Dart sin tener que escapar todas las barras invertidas.

Autovalidación

Por defecto, los errores solo salen cuando pulsas el botón “Enviar”. A veces es mejor ir avisando al usuario mientras escribe.

Podemos cambiar el comportamiento con autovalidateMode.

Form(
  key: _formKey,
  // Valida en cuanto el usuario interactúa con el campo
  autovalidateMode: AutovalidateMode.onUserInteraction, 
  child: ...
)
Copied!

Esto mejora mucho la experiencia de usuario, ya que el mensaje rojo desaparece en el instante en que el usuario corrige el error, sin tener que volver a pulsar “Enviar”.