Flutter Forms and Validation

This section will introduce the creation, validation, and submission handling of forms in Flutter.


TextFormField - Form Input

TextFormField is a TextField specifically designed for forms, with built-in validation support.

Example: Basic Form

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

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  // Form key (for validation)
  final _formKey = GlobalKey<FormState>();

  // Input controllers
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void dispose() {
    _usernameController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          // Username input
          TextFormField(
            controller: _usernameController,
            decoration: const InputDecoration(
              labelText: Username,
              prefixIcon: Icon(Icons.person),
            ),
            // Validator
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter username';
              }
              if (value.length < 3) {
                return 'Username must be at least 3 characters';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          // Password input
          TextFormField(
            controller: _passwordController,
            obscureText: true,  // Hide password
            decoration: const InputDecoration(
              labelText: Password,
              prefixIcon: Icon(Icons.lock),
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter password';
              }
              if (value.length < 6) {
                return 'Password must be at least 6 characters';
              }
              return null;
            },
          ),
          const SizedBox(height: 24),
          // Submit button
          ElevatedButton(
            onPressed: _submit,
            child: const Text('Login'),
          ),
        ],
      ),
    );
  }

  void _submit() {
    // Validate form
    if (_formKey.currentState?.validate() ?? false) {
      // Validation passed
      print('Username: ${_usernameController.text}');
      print('Password: ${_passwordController.text}');
      // Submit to server...
    } else {
      // Validation failed
      print('Form validation failed');
    }
  }
}

Common validators

Example: Common Validation Rules

// Validate username
String? validateUsername(String? value) {
  if (value == null || value.isEmpty) {
    return 'Please enter username';
  }
  if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
    return 'Can only contain letters, numbers, and underscores';
  }
  return null;
}

// Validate email
String? validateEmail(String? value) {
  if (value == null || value.isEmpty) {
    return 'Please enter email';
  }
  final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
  if (!emailRegex.hasMatch(value)) {
    return Please enter a valid email address;
  }
  return null;
}

// Validate password strength
String? validatePassword(String? value) {
  if (value == null || value.isEmpty) {
    return 'Please enter password';
  }
  if (value.length < 8) {
    return 'Password must be at least 8 characters';
  }
  if (!RegExp(r'[A-Z]').hasMatch(value)) {
    return 'Password must contain at least one uppercase letter';
  }
  if (!RegExp(r'[0-9]').hasMatch(value)) {
    return 'Password must contain at least one number';
  }
  return null;
}

// Validate phone number
String? validatePhone(String? value) {
  if (value == null || value.isEmpty) {
    return 'Please enter phone number';
  }
  final phoneRegex = RegExp(r'^1[3-9]\d{9}$');
  if (!phoneRegex.hasMatch(value)) {
    return 'Please enter a valid phone number';
  }
  return null;
}

// Validate passwords match
String? validateConfirmPassword(String? value, String password) {
  if (value == null || value.isEmpty) {
    return 'Please confirm password';
  }
  if (value != password) {
    return 'The two passwords do not match';
  }
  return null;
}

Complex Form Example

Example: Registration Form

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

  @override
  State<RegisterForm> createState() => _RegisterFormState();
}

class _RegisterFormState extends State<RegisterForm> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  final _emailController = TextEditingController();
  final _phoneController = TextEditingController();
  final _passwordController = TextEditingController();
  final _confirmPasswordController = TextEditingController();

  bool _acceptTerms = false;

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Username
          TextFormField(
            controller: _usernameController,
            decoration: const InputDecoration(labelText: Username),
            validator: validateUsername,
          ),
          const SizedBox(height: 16),
          // Email
          TextFormField(
            controller: _emailController,
            keyboardType: TextInputType.emailAddress,
            decoration: const InputDecoration(labelText: Email),
            validator: validateEmail,
          ),
          const SizedBox(height: 16),
          // Phone number
          TextFormField(
            controller: _phoneController,
            keyboardType: TextInputType.phone,
            decoration: const InputDecoration(labelText: 'Phone number'),
            validator: validatePhone,
          ),
          const SizedBox(height: 16),
          // Password
          TextFormField(
            controller: _passwordController,
            obscureText: true,
            decoration: const InputDecoration(labelText: Password),
            validator: validatePassword,
          ),
          const SizedBox(height: 16),
          // Confirm password
          TextFormField(
            controller: _confirmPasswordController,
            obscureText: true,
            decoration: const InputDecoration(labelText: Confirm password),
            validator: (value) => validateConfirmPassword(
              value,
              _passwordController.text,
            ),
          ),
          const SizedBox(height: 16),
          // Agree to terms
          CheckboxListTile(
            value: _acceptTerms,
            onChanged: (value) {
              setState(() {
                _acceptTerms = value ?? false;
              });
            },
            title: const Text(I agree to the Terms of Service),
            controlAffinity: ListTileControlAffinity.leading,
          ),
          const SizedBox(height: 24),
          // Submit button
          ElevatedButton(
            onPressed: _acceptTerms ? _submit : null,
            child: const Text(Register),
          ),
        ],
      ),
    );
  }

  void _submit() {
    if (_formKey.currentState?.validate() ?? false) {
      // Submit form
      print(Registration successful!);
    }
  }

  @override
  void dispose() {
    _usernameController.dispose();
    _emailController.dispose();
    _phoneController.dispose();
    _passwordController.dispose();
    _confirmPasswordController.dispose();
    super.dispose();
  }
}

Form validation should be performed on both the client side (for user experience) and the server side (for security); do not rely solely on client-side validation.

other extensions