Flutter Forms and User Input

Forms collect information from users — login details, signup data, search queries. Flutter provides a powerful Form widget with built-in validation to make this process clean and reliable.

The TextField Widget

TextField is the basic text input widget. It shows a keyboard and accepts user typing.

TextField(
  decoration: InputDecoration(
    labelText: 'Email',
    hintText: 'Enter your email',
    prefixIcon: Icon(Icons.email),
    border: OutlineInputBorder(),
  ),
  keyboardType: TextInputType.emailAddress,
  onChanged: (value) {
    print('User typed: $value');
  },
)

Keyboard Types

  TextInputType.text          → Standard keyboard
  TextInputType.emailAddress  → Shows @ and .com
  TextInputType.number        → Number pad
  TextInputType.phone         → Phone pad
  TextInputType.multiline     → Multi-line text area

Reading TextField Value with TextEditingController

To read what the user typed, attach a TextEditingController to the field.

class LoginScreen extends StatefulWidget {
  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  void dispose() {
    // Always dispose controllers to free memory
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _login() {
    String email = _emailController.text;
    String password = _passwordController.text;
    print('Email: $email, Password: $password');
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: _emailController,
          decoration: InputDecoration(labelText: 'Email'),
        ),
        TextField(
          controller: _passwordController,
          obscureText: true,  // Hides password characters
          decoration: InputDecoration(labelText: 'Password'),
        ),
        ElevatedButton(onPressed: _login, child: Text('Login')),
      ],
    );
  }
}

Form Widget with Validation

The Form widget wraps multiple TextFormField widgets and validates them all at once with a single method call.

  Form (validator coordinator)
  └── TextFormField (email, with validator)
  └── TextFormField (password, with validator)
  └── ElevatedButton (calls form.validate())
class SignupForm extends StatefulWidget {
  @override
  State<SignupForm> createState() => _SignupFormState();
}

class _SignupFormState extends State<SignupForm> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();

  void _submit() {
    if (_formKey.currentState!.validate()) {
      // All fields passed validation
      print('Form is valid!');
      print('Name: ${_nameController.text}');
      print('Email: ${_emailController.text}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _nameController,
            decoration: InputDecoration(labelText: 'Full Name'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your name';
              }
              return null;  // null means valid
            },
          ),
          TextFormField(
            controller: _emailController,
            decoration: InputDecoration(labelText: 'Email'),
            keyboardType: TextInputType.emailAddress,
            validator: (value) {
              if (value == null || !value.contains('@')) {
                return 'Enter a valid email address';
              }
              return null;
            },
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: _submit,
            child: Text('Sign Up'),
          ),
        ],
      ),
    );
  }
}

Validation Flow Diagram

  User taps "Sign Up"
         ↓
  _formKey.currentState!.validate() called
         ↓
  Each TextFormField runs its validator()
         ↓
  ┌──────────────────────────────────┐
  │ validator returns null?          │
  │  YES → field is valid ✓          │
  │  NO  → show error message ✗      │
  └──────────────────────────────────┘
         ↓
  All fields valid? → run _submit logic
  Any field invalid? → show errors, stop

Other Input Widgets

Checkbox

bool _agreed = false;

Checkbox(
  value: _agreed,
  onChanged: (value) {
    setState(() { _agreed = value!; });
  },
)

Switch

bool _darkMode = false;

Switch(
  value: _darkMode,
  onChanged: (value) {
    setState(() { _darkMode = value; });
  },
)

DropdownButton

String _selectedCity = 'Mumbai';

DropdownButton<String>(
  value: _selectedCity,
  items: ['Mumbai', 'Delhi', 'Bangalore']
      .map((city) => DropdownMenuItem(value: city, child: Text(city)))
      .toList(),
  onChanged: (value) {
    setState(() { _selectedCity = value!; });
  },
)

Slider

double _rating = 3;

Slider(
  value: _rating,
  min: 1,
  max: 5,
  divisions: 4,
  label: _rating.toString(),
  onChanged: (value) {
    setState(() { _rating = value; });
  },
)

Showing a Snackbar After Form Submit

A Snackbar is a brief message that appears at the bottom of the screen after an action.

ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('Signup successful!'),
    backgroundColor: Colors.green,
    duration: Duration(seconds: 3),
  ),
);

Best Practices for Forms

  • Always dispose TextEditingController objects in dispose().
  • Use Form with a GlobalKey for multi-field validation.
  • Set obscureText: true on password fields.
  • Use the correct keyboardType to make entry easier for users.
  • Show clear, friendly error messages — never technical jargon.

Leave a Comment

Your email address will not be published. Required fields are marked *