DEV Community

Ge Ji
Ge Ji

Posted on

Flutter Lesson 10: Forms and Inputs

In mobile application development, forms are essential for collecting user input. Whether it's for login/registration, personal information entry, or data submission, form components are indispensable. Flutter provides a comprehensive form handling mechanism, including form widgets, input validation, and data processing capabilities. This lesson will detailedly introduce form and input-related components in Flutter, helping you build fully functional and user-friendly form interfaces.

I. Basic Form Components

Flutter provides the Form widget as a container for forms, which works with various input controls (such as TextFormField) to implement complete form functionality. The Form widget itself doesn't render any visible content; it's primarily used for managing the state, validation, and submission of form fields.

1. Form Widget and GlobalKey

The Form widget needs a GlobalKey to manage form state, enabling form validation and data submission. A GlobalKey is a way to access state across widgets, uniquely identifying a widget and retrieving its state.

Basic usage example:

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

  @override
  State<BasicFormExample> createState() => _BasicFormExampleState();
}

class _BasicFormExampleState extends State<BasicFormExample> {
  // Create a global key for the form
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Basic Form')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        // Form widget
        child: Form(
          key: _formKey, // Associate with the global key
          autovalidateMode: AutovalidateMode.onUserInteraction, // Validation mode
          child: Column(
            children: [
              // Form field
              TextFormField(
                decoration: const InputDecoration(
                  labelText: 'Username',
                  border: OutlineInputBorder(),
                ),
                // Validator
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your username';
                  }
                  return null; // Validation passed
                },
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () {
                  // Validate the form
                  if (_formKey.currentState!.validate()) {
                    // Validation passed, process data
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Processing data')),
                    );
                  }
                },
                child: const Text('Submit'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Core parameters of the Form widget:

  • key: A GlobalKey used to access the form state
  • autovalidateMode: Automatic validation mode, determining when to validate input automatically
    • AutovalidateMode.disabled: Disable automatic validation (default)
    • AutovalidateMode.always: Always validate automatically
    • AutovalidateMode.onUserInteraction: Validate on user interaction (e.g., input, focus change)

2. TextFormField Widget

TextFormField is the most commonly used input widget in forms. It inherits from TextField and adds form validation capabilities. It supports various input types (text, numbers, email, etc.) and can be customized in appearance and behavior.

Example of common properties:

TextFormField(
  // Input box decoration
  decoration: InputDecoration(
    labelText: 'Email',
    hintText: 'Enter your email address',
    prefixIcon: const Icon(Icons.email),
    border: const OutlineInputBorder(),
    // Error提示样式
    errorBorder: OutlineInputBorder(
      borderSide: const BorderSide(color: Colors.red),
      borderRadius: BorderRadius.circular(4),
    ),
  ),
  // Input type
  keyboardType: TextInputType.emailAddress,
  // Input formatters (can restrict input content)
  inputFormatters: [
    FilteringTextInputFormatter.deny(RegExp(r'\s')), // Deny spaces
  ],
  // Text capitalization
  textCapitalization: TextCapitalization.none,
  // Password obfuscation
  obscureText: false, // Set to true for password fields
  // Autocorrect
  autocorrect: false,
  // Autofocus
  autofocus: false,
  // Maximum length
  maxLength: 50,
  // Maximum lines
  maxLines: 1,
  // Validator
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter your email';
    }
    // Simple email format validation
    if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
      return 'Please enter a valid email';
    }
    return null;
  },
  // Input change callback
  onChanged: (value) {
    print('Email changed: $value');
  },
  // Input completion callback
  onFieldSubmitted: (value) {
    print('Email submitted: $value');
  },
)
Enter fullscreen mode Exit fullscreen mode

3. Other Input Widgets

In addition to text input, Flutter provides other commonly used form input widgets:

  • Checkbox: For selecting multiple options
  • Radio: For selecting one option from multiple choices
  • Switch: For turning a feature on/off
  • DropdownButton: For selecting from preset options

These widgets can be wrapped with FormField to integrate into forms:

// Checkbox form field
FormField<bool>(
  initialValue: false,
  validator: (value) {
    if (value == false) {
      return 'Please agree to the terms';
    }
    return null;
  },
  builder: (formFieldState) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Checkbox(
              value: formFieldState.value,
              onChanged: (value) {
                formFieldState.didChange(value);
              },
            ),
            const Text('I agree to the terms and conditions'),
          ],
        ),
        if (formFieldState.hasError)
          Padding(
            padding: const EdgeInsets.only(top: 4, left: 4),
            child: Text(
              formFieldState.errorText!,
              style: const TextStyle(
                color: Colors.red,
                fontSize: 12,
              ),
            ),
          ),
      ],
    );
  },
)

// Dropdown selection box
FormField<String>(
  initialValue: 'male',
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please select gender';
    }
    return null;
  },
  builder: (formFieldState) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        DropdownButton<String>(
          value: formFieldState.value,
          isExpanded: true,
          items: const [
            DropdownMenuItem(value: 'male', child: Text('Male')),
            DropdownMenuItem(value: 'female', child: Text('Female')),
            DropdownMenuItem(value: 'other', child: Text('Other')),
          ],
          onChanged: (value) {
            formFieldState.didChange(value);
          },
        ),
        if (formFieldState.hasError)
          Padding(
            padding: const EdgeInsets.only(top: 4, left: 4),
            child: Text(
              formFieldState.errorText!,
              style: const TextStyle(
                color: Colors.red,
                fontSize: 12,
              ),
            ),
          ),
      ],
    );
  },
)
Enter fullscreen mode Exit fullscreen mode

II. Input Validation and Form Submission

Form validation is crucial for ensuring user input meets requirements. Flutter provides a flexible validation mechanism that can implement various validation logic from simple to complex.

1. Basic Validation Logic

Each TextFormField can specify a validation function through the validator property. This function receives the input value and returns an error message (validation failed) or null (validation succeeded).

Examples of common validations:

// Username validation
validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please enter username';
  }
  if (value.length < 3) {
    return 'Username must be at least 3 characters';
  }
  if (value.length > 20) {
    return 'Username cannot exceed 20 characters';
  }
  return null;
}

// Password validation
validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please enter password';
  }
  if (value.length < 6) {
    return 'Password must be at least 6 characters';
  }
  if (!RegExp(r'[A-Z]').hasMatch(value)) {
    return 'Password must contain at least one uppercase letter';
  }
  return null;
}

// Confirm password validation (needs comparison with password field)
validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please confirm password';
  }
  if (value != _passwordController.text) {
    return 'Passwords do not match';
  }
  return null;
}
Enter fullscreen mode Exit fullscreen mode

2. Form Submission and Data Processing

Form validation and data retrieval can be triggered through GlobalKey:

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

  @override
  State<FormSubmissionExample> createState() => _FormSubmissionExampleState();
}

class _FormSubmissionExampleState extends State<FormSubmissionExample> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  final _emailController = TextEditingController();

  @override
  void dispose() {
    // Release controller resources
    _usernameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  // Submit the form
  void _submitForm() {
    // Validate all fields
    if (_formKey.currentState!.validate()) {
      // Validation passed, save form state
      _formKey.currentState!.save();

      // Get input values
      final username = _usernameController.text;
      final email = _emailController.text;

      // Process form data (e.g., submit to server)
      _processFormData(username, email);
    }
  }

  // Process form data
  void _processFormData(String username, String email) {
    print('Username: $username');
    print('Email: $email');

    // Show submission success message
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Form submitted successfully')),
    );

    // Can navigate to other pages here
    // Navigator.pushNamed(context, '/success');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Form Submission')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          autovalidateMode: AutovalidateMode.onUserInteraction,
          child: Column(
            children: [
              TextFormField(
                controller: _usernameController,
                decoration: const InputDecoration(
                  labelText: 'Username',
                  border: OutlineInputBorder(),
                ),
                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;
                },
                // Save callback (triggered when save() is called)
                onSaved: (value) {
                  print('Username saved: $value');
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter email';
                  }
                  if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
                    return 'Please enter a valid email';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: _submitForm,
                child: const Text('Submit'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Form processing flow:

  1. User enters data
  2. Validation is triggered (manually or automatically)
  3. User clicks the submit button
  4. Call _formKey.currentState!.validate() to validate all fields
  5. After successful validation, call _formKey.currentState!.save() to save all field values
  6. Retrieve input data and process it (e.g., submit to server)

3. Manually Controlling Validation Timing

In addition to automatic validation, you can also manually control when validation occurs:

// Only validate on submission
Form(
  key: _formKey,
  autovalidateMode: AutovalidateMode.disabled, // Disable automatic validation
  // ...
)

// Manually trigger validation for a single field
void _validateUsername() {
  _usernameFieldKey.currentState?.validate();
}

// Reset the form
void _resetForm() {
  _formKey.currentState?.reset();
}
Enter fullscreen mode Exit fullscreen mode

III. Text Controller: TextEditingController

TextEditingController is used to control the content of text input widgets. It can retrieve, set, and listen to changes in input content, making it an important tool for handling form data.

1. Basic Usage

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

  @override
  State<TextEditingControllerExample> createState() => _TextEditingControllerExampleState();
}

class _TextEditingControllerExampleState extends State<TextEditingControllerExample> {
  // Create text controller
  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();

    // Set initial value
    _controller.text = 'Initial value';

    // Listen to text changes
    _controller.addListener(_onTextChanged);
  }

  @override
  void dispose() {
    // Remove listener and release resources
    _controller.removeListener(_onTextChanged);
    _controller.dispose();
    super.dispose();
  }

  // Text change callback
  void _onTextChanged() {
    print('Text changed: ${_controller.text}');
  }

  // Get input value
  void _getValue() {
    final text = _controller.text;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Current value: $text')),
    );
  }

  // Set input value
  void _setValue() {
    _controller.text = 'New value set programmatically';
  }

  // Clear input
  void _clearValue() {
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Text Controller')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _controller, // Associate with controller
              decoration: const InputDecoration(
                labelText: 'Enter text',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                ElevatedButton(
                  onPressed: _getValue,
                  child: const Text('Get Value'),
                ),
                ElevatedButton(
                  onPressed: _setValue,
                  child: const Text('Set Value'),
                ),
                ElevatedButton(
                  onPressed: _clearValue,
                  child: const Text('Clear'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Advanced Usage

TextEditingController also provides some advanced features:

  • Selecting text:
// Select text
_controller.selection = TextSelection(
  baseOffset: 2,
  extentOffset: 5,
);

// Select all text
_controller.selection = TextSelection(
  baseOffset: 0,
  extentOffset: _controller.text.length,
);
Enter fullscreen mode Exit fullscreen mode
  • Setting cursor position:
// Set cursor position to the end
_controller.selection = TextSelection.fromPosition(
  TextPosition(offset: _controller.text.length),
);
Enter fullscreen mode Exit fullscreen mode
  • Styled text: Using TextSpan to set rich text:
final _richTextController = TextEditingController(
  text: 'Hello World',
);

// In build method
TextField(
  controller: _richTextController,
  decoration: const InputDecoration(labelText: 'Rich Text'),
  style: const TextStyle(fontSize: 16),
  // Can restrict input format through inputFormatters
)
Enter fullscreen mode Exit fullscreen mode

IV. Example: Implementing Login and Registration Forms

The following implements complete login and registration forms with features including input validation, form submission, and password show/hide toggle.

1. Login Form

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

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

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _obscurePassword = true; // Controls whether password is visible
  bool _isLoading = false; // Controls loading state

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

  // Toggle password visibility
  void _togglePasswordVisibility() {
    setState(() {
      _obscurePassword = !_obscurePassword;
    });
  }

  // Submit login form
  Future<void> _submitLogin() async {
    if (_formKey.currentState!.validate()) {
      setState(() {
        _isLoading = true;
      });

      // Simulate login request
      try {
        await Future.delayed(const Duration(seconds: 2));
        // Login successful, navigate to home page
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Login successful')),
          );
          // Navigator.pushReplacementNamed(context, '/home');
        }
      } catch (e) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Login failed: $e')),
          );
        }
      } finally {
        if (mounted) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(
              labelText: 'Email',
              prefixIcon: Icon(Icons.email),
              border: OutlineInputBorder(),
            ),
            keyboardType: TextInputType.emailAddress,
            enabled: !_isLoading,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your email';
              }
              if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
                return 'Please enter a valid email';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          TextFormField(
            controller: _passwordController,
            decoration: InputDecoration(
              labelText: 'Password',
              prefixIcon: const Icon(Icons.lock),
              border: const OutlineInputBorder(),
              // Password visibility toggle button
              suffixIcon: IconButton(
                icon: Icon(
                  _obscurePassword ? Icons.visibility : Icons.visibility_off,
                ),
                onPressed: _togglePasswordVisibility,
              ),
            ),
            obscureText: _obscurePassword,
            enabled: !_isLoading,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your password';
              }
              if (value.length < 6) {
                return 'Password must be at least 6 characters';
              }
              return null;
            },
          ),
          const SizedBox(height: 8),
          // Forgot password link
          Align(
            alignment: Alignment.centerRight,
            child: TextButton(
              onPressed: () {
                // Navigate to forgot password page
                // Navigator.pushNamed(context, '/forgot-password');
              },
              child: const Text('Forgot Password?'),
            ),
          ),
          const SizedBox(height: 16),
          // Login button
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: _isLoading ? null : _submitLogin,
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(vertical: 16),
              ),
              child: _isLoading
                  ? const CircularProgressIndicator(color: Colors.white)
                  : const Text('Login'),
            ),
          ),
          const SizedBox(height: 16),
          // Register link
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text("Don't have an account?"),
              TextButton(
                onPressed: () {
                  // Navigate to register page
                  // Navigator.pushNamed(context, '/register');
                },
                child: const Text('Register'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

2. 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 _passwordController = TextEditingController();
  final _confirmPasswordController = TextEditingController();

  bool _obscurePassword = true;
  bool _obscureConfirmPassword = true;
  bool _isLoading = false;
  String? _selectedGender;

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

  void _togglePasswordVisibility() {
    setState(() {
      _obscurePassword = !_obscurePassword;
    });
  }

  void _toggleConfirmPasswordVisibility() {
    setState(() {
      _obscureConfirmPassword = !_obscureConfirmPassword;
    });
  }

  Future<void> _submitRegistration() async {
    if (_formKey.currentState!.validate()) {
      setState(() {
        _isLoading = true;
      });

      // Simulate registration request
      try {
        await Future.delayed(const Duration(seconds: 2));
        // Registration successful
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Registration successful')),
          );
          Navigator.pop(context); // Return to login page
        }
      } catch (e) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Registration failed: $e')),
          );
        }
      } finally {
        if (mounted) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: SingleChildScrollView(
        child: Column(
          children: [
            TextFormField(
              controller: _usernameController,
              decoration: const InputDecoration(
                labelText: 'Username',
                prefixIcon: Icon(Icons.person),
                border: OutlineInputBorder(),
              ),
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your username';
                }
                if (value.length < 3) {
                  return 'Username must be at least 3 characters';
                }
                if (value.length > 20) {
                  return 'Username cannot exceed 20 characters';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _emailController,
              decoration: const InputDecoration(
                labelText: 'Email',
                prefixIcon: Icon(Icons.email),
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.emailAddress,
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your email';
                }
                if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
                  return 'Please enter a valid email';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            // Gender selection
            DropdownButtonFormField<String>(
              value: _selectedGender,
              decoration: const InputDecoration(
                labelText: 'Gender',
                prefixIcon: Icon(Icons.person_2),
                border: OutlineInputBorder(),
              ),
              items: const [
                DropdownMenuItem(value: 'male', child: Text('Male')),
                DropdownMenuItem(value: 'female', child: Text('Female')),
                DropdownMenuItem(value: 'other', child: Text('Other')),
              ],
              onChanged: !_isLoading ? (value) {
                setState(() {
                  _selectedGender = value;
                });
              } : null,
              validator: (value) {
                if (value == null) {
                  return 'Please select your gender';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _passwordController,
              decoration: InputDecoration(
                labelText: 'Password',
                prefixIcon: const Icon(Icons.lock),
                border: const OutlineInputBorder(),
                suffixIcon: IconButton(
                  icon: Icon(
                    _obscurePassword ? Icons.visibility : Icons.visibility_off,
                  ),
                  onPressed: _togglePasswordVisibility,
                ),
              ),
              obscureText: _obscurePassword,
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your password';
                }
                if (value.length < 6) {
                  return 'Password must be at least 6 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;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _confirmPasswordController,
              decoration: InputDecoration(
                labelText: 'Confirm Password',
                prefixIcon: const Icon(Icons.lock_clock),
                border: const OutlineInputBorder(),
                suffixIcon: IconButton(
                  icon: Icon(
                    _obscureConfirmPassword ? Icons.visibility : Icons.visibility_off,
                  ),
                  onPressed: _toggleConfirmPasswordVisibility,
                ),
              ),
              obscureText: _obscureConfirmPassword,
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please confirm your password';
                }
                if (value != _passwordController.text) {
                  return 'Passwords do not match';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            // Agree to terms
            FormField<bool>(
              initialValue: false,
              validator: (value) {
                if (value == false) {
                  return 'Please agree to the terms';
                }
                return null;
              },
              builder: (formFieldState) {
                return Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Checkbox(
                          value: formFieldState.value,
                          onChanged: _isLoading ? null : (value) {
                            formFieldState.didChange(value);
                          },
                        ),
                        const Text('I agree to the Terms of Service and Privacy Policy'),
                      ],
                    ),
                    if (formFieldState.hasError)
                      Padding(
                        padding: const EdgeInsets.only(top: 4, left: 4),
                        child: Text(
                          formFieldState.errorText!,
                          style: const TextStyle(
                            color: Colors.red,
                            fontSize: 12,
                          ),
                        ),
                      ),
                  ],
                );
              },
            ),
            const SizedBox(height: 24),
            // Register button
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _isLoading ? null : _submitRegistration,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
                child: _isLoading
                    ? const CircularProgressIndicator(color: Colors.white)
                    : const Text('Register'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Form Page Integration

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

  @override
  State<AuthScreen> createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  bool _isLogin = true; // Toggle between login/register modes

  // Toggle form mode
  void _toggleFormMode() {
    setState(() {
      _isLogin = !_isLogin;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Title
            Text(
              _isLogin ? 'Welcome Back' : 'Create Account',
              style: const TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              _isLogin 
                  ? 'Sign in to your account to continue' 
                  : 'Fill in the details to create a new account',
              style: const TextStyle(
                color: Colors.grey,
                fontSize: 16,
              ),
            ),
            const SizedBox(height: 32),
            // Show login or register form
            _isLogin ? const LoginForm() : const RegisterForm(),
            // Toggle form button
            if (!_isLogin)
              TextButton(
                onPressed: _toggleFormMode,
                child: const Text('Already have an account? Login'),
              ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)