In Flutter, validating user inputs is a crucial aspect of building robust and user-friendly applications. Custom validation logic allows you to enforce specific business rules and constraints on user data. Additionally, displaying informative error messages helps guide users in correcting their input, leading to a better user experience. This article delves into creating custom validation logic and effectively displaying error messages in Flutter forms.
Understanding Form Validation in Flutter
Flutter’s Form
widget, combined with TextFormField
, provides a straightforward way to manage and validate user input. Each TextFormField
can have a validator
property that takes a function responsible for validating the input. When the form’s validate()
method is called, each validator is executed. If any validator returns a non-null string, it signifies an error, and the string is displayed as an error message.
Why Use Custom Validation Logic?
- Business Rule Enforcement: Validate data according to specific business rules.
- Data Integrity: Ensure data conforms to required formats and constraints.
- User Guidance: Provide clear error messages that help users correct mistakes.
Implementing Custom Validation Logic
To implement custom validation logic, create a function that checks specific conditions and returns an error message (string) if the validation fails, or null
if it passes.
Step 1: Set up a Basic Form
First, let’s create a simple form with a TextFormField
for an email and a TextFormField
for a password.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom Validation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Custom Validation Demo'),
),
body: MyCustomForm(),
),
);
}
}
class MyCustomForm extends StatefulWidget {
@override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State {
final _formKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your password',
labelText: 'Password',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
obscureText: true,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: const Text('Submit'),
),
),
],
),
),
);
}
}
Step 2: Implement Custom Validation Functions
Now, let’s add custom validation functions for email and password.
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
String pattern = r'\w+@\w+\.\w+';
RegExp regex = RegExp(pattern);
if (!regex.hasMatch(value)) {
return 'Please enter a valid email address';
}
return null;
}
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
if (!value.contains(RegExp(r'[A-Z]'))) {
return 'Password must contain at least one uppercase letter';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return 'Password must contain at least one digit';
}
return null;
}
Step 3: Integrate Custom Validators into the Form
Integrate these validation functions into your TextFormField
widgets.
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
validator: validateEmail,
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your password',
labelText: 'Password',
),
validator: validatePassword,
obscureText: true,
),
Displaying Error Messages
Flutter automatically displays the error message returned by the validator
in the TextFormField
. However, customizing the error display can enhance the user experience.
Customizing Error Messages
You can customize the appearance of error messages using the InputDecoration
class within the TextFormField
. This includes changing the error style, error border, and error text style.
TextFormField(
decoration: InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
errorStyle: TextStyle(color: Colors.red),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
),
validator: validateEmail,
),
Handling Validation Logic Separately
For complex validation logic, consider moving the validation functions outside the TextFormField
widget for better readability and maintainability.
class _MyCustomFormState extends State {
final _formKey = GlobalKey();
String? _email;
String? _password;
void _submitForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
print('Email: $_email, Password: $_password');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
validator: validateEmail,
onSaved: (value) => _email = value,
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your password',
labelText: 'Password',
),
validator: validatePassword,
obscureText: true,
onSaved: (value) => _password = value,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: _submitForm,
child: const Text('Submit'),
),
),
],
),
),
);
}
}
Advanced Validation Scenarios
Conditional Validation
Sometimes, validation depends on other form fields. Use the current state of the form to perform conditional validation.
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your confirm password',
labelText: 'Confirm Password',
),
validator: (value) {
if (value != _password) {
return 'Passwords do not match';
}
return null;
},
obscureText: true,
),
Real-Time Validation
For immediate feedback, validate the input as the user types using the onChanged
property of the TextFormField
.
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your email',
labelText: 'Email',
),
onChanged: (value) {
_formKey.currentState?.validate();
},
validator: validateEmail,
),
Conclusion
Custom validation logic and effective error message display are essential for creating robust and user-friendly Flutter forms. By implementing custom validation functions and utilizing the InputDecoration
class for customized error messages, you can guide users to input valid data and enhance the overall user experience. Incorporate these techniques into your Flutter applications to ensure data integrity and improve form usability.