Custom validators help check if user input is correct in ways built-in checks can't. They make sure your app gets good data.
Custom validators in Flutter
String? customValidator(String? value) { if (value == null || value.isEmpty) { return 'This field cannot be empty'; } if (!value.contains('@')) { return 'Must contain @ symbol'; } return null; // null means input is valid }
The validator function returns a String?. If it returns null, the input is valid.
If it returns a String, that string is shown as an error message.
String? passwordValidator(String? value) { if (value == null || value.length < 6) { return 'Password must be at least 6 characters'; } return null; }
String? usernameValidator(String? value) { if (value == null || value.isEmpty) { return 'Username cannot be empty'; } if (value.contains(' ')) { return 'Username cannot contain spaces'; } return null; }
This app shows a form with an email field. The custom validator checks if the email is empty or missing '@'. If invalid, it shows an error below the field. If valid, it shows a success message.
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Custom Validator Example')), body: const Padding( padding: EdgeInsets.all(16), child: MyForm(), ), ), ); } } class MyForm extends StatefulWidget { const MyForm({super.key}); @override State<MyForm> createState() => _MyFormState(); } class _MyFormState extends State<MyForm> { final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); String? emailValidator(String? value) { if (value == null || value.isEmpty) { return 'Email cannot be empty'; } if (!value.contains('@')) { return 'Email must contain @'; } return null; } @override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( children: [ TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email'), validator: emailValidator, ), const SizedBox(height: 20), ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Email is valid!')), ); } }, child: const Text('Submit'), ), ], ), ); } }
Always return null from your validator when the input is valid.
Use TextFormField with a Form widget to use validators easily.
Call _formKey.currentState!.validate() to run all validators and check if form is valid.
Custom validators check user input with your own rules.
Return a string error message if input is wrong, or null if input is good.
Use validators inside TextFormField and call validate() to check.
Practice
Solution
Step 1: Understand validator return type
A custom validator returns a String error message if input is invalid, otherwise null.Step 2: Identify valid input return
When input is valid, the validator returns null to indicate no error.Final Answer:
null -> Option BQuick Check:
Valid input returns null [OK]
- Returning empty string instead of null
- Returning boolean true/false
- Returning error message on valid input
Solution
Step 1: Check correct Dart syntax for validator
The validator must return a String or null. validator: (value) => value!.isEmpty ? 'Enter text' : null uses a concise lambda with a ternary operator correctly.Step 2: Identify null safety and return values
validator: (value) => value!.isEmpty ? 'Enter text' : null uses value! to assert non-null and returns error string if empty, else null.Final Answer:
validator: (value) => value!.isEmpty ? 'Enter text' : null -> Option DQuick Check:
Correct syntax uses ternary and returns null on valid input [OK]
- Missing return statement in block syntax
- Using if without else in lambda
- Returning null on empty input instead of error
validator: (value) {
if (value == null || value.length < 5) {
return 'Too short';
}
return null;
}
What happens if the user enters "abc"?Solution
Step 1: Analyze input length check
The validator checks if value is null or length less than 5, then returns 'Too short'.Step 2: Apply input "abc" to condition
"abc" length is 3, which is less than 5, so validator returns 'Too short'.Final Answer:
The form shows error message 'Too short' -> Option CQuick Check:
Input length < 5 triggers error message [OK]
- Ignoring null check and assuming no error
- Confusing return null with error message
- Assuming validator crashes on short input
validator: (value) {
if (value.isEmpty) {
return 'Cannot be empty';
}
}Solution
Step 1: Check null safety on value
value is nullable String?, so calling value.isEmpty without null check causes error.Step 2: Identify missing null check
Must check if value is null before calling isEmpty to avoid runtime error.Final Answer:
Using isEmpty on nullable value without null check -> Option AQuick Check:
Always check for null before isEmpty on nullable String [OK]
- Assuming value is non-null without checking
- Not returning null for valid input (though important)
- Confusing return types of validator
Solution
Step 1: Check null and '@' presence
validator: (value) { if (value == null || !value.contains('@')) return 'Invalid email'; if (!value.endsWith('.com')) return 'Must end with .com'; return null; } first checks if value is null or missing '@', returning 'Invalid email'.Step 2: Check if ends with '.com'
Then it checks if value ends with '.com', returning specific error if not.Step 3: Return null if all checks pass
If both checks pass, it returns null indicating valid input.Final Answer:
validator: (value) { if (value == null || !value.contains('@')) return 'Invalid email'; if (!value.endsWith('.com')) return 'Must end with .com'; return null; } -> Option AQuick Check:
Separate checks with clear error messages [OK]
- Combining conditions incorrectly with && or ||
- Returning null on null input without error
- Not checking null before calling methods
Ready to prove it?
Pass the test and this pattern moves unknown
