Skip to main content

One post tagged with "Validation"

View All Tags

Flutter: Quản lý form và validation

· 8 min read

Form là một phần quan trọng trong hầu hết các ứng dụng. Bài viết này sẽ hướng dẫn cách quản lý form và thực hiện validation trong Flutter.

Form Validation

1. Form Widget

1.1. Cấu trúc cơ bản

class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Username',
hintText: 'Enter your username',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your username';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Xử lý form
}
},
child: Text('Submit'),
),
],
),
);
}
}

1.2. FormField Widget

class CustomFormField extends FormField<String> {
CustomFormField({
Key? key,
required String label,
required String? Function(String?) validator,
void Function(String?)? onSaved,
}) : super(
key: key,
validator: validator,
onSaved: onSaved,
builder: (FormFieldState<String> state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
TextField(
onChanged: (value) {
state.didChange(value);
},
decoration: InputDecoration(
errorText: state.errorText,
),
),
],
);
},
);
}

2. Validation

2.1. Validation cơ bản

class ValidationForm extends StatefulWidget {
@override
_ValidationFormState createState() => _ValidationFormState();
}

class _ValidationFormState extends State<ValidationForm> {
final _formKey = GlobalKey<FormState>();
String _email = '';
String _password = '';

String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
}

String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: _validateEmail,
onSaved: (value) => _email = value ?? '',
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: _validatePassword,
onSaved: (value) => _password = value ?? '',
),
ElevatedButton(
onPressed: _submitForm,
child: Text('Submit'),
),
],
),
);
}

void _submitForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// Xử lý form
}
}
}

2.2. Validation nâng cao

class AdvancedValidationForm extends StatefulWidget {
@override
_AdvancedValidationFormState createState() => _AdvancedValidationFormState();
}

class _AdvancedValidationFormState extends State<AdvancedValidationForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();

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

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

String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
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 number';
}
return null;
}

String? _validateConfirmPassword(String? value) {
if (value != _passwordController.text) {
return 'Passwords do not match';
}
return null;
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: _validateEmail,
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: _validatePassword,
),
TextFormField(
controller: _confirmPasswordController,
decoration: InputDecoration(
labelText: 'Confirm Password',
prefixIcon: Icon(Icons.lock_outline),
),
obscureText: true,
validator: _validateConfirmPassword,
),
ElevatedButton(
onPressed: _submitForm,
child: Text('Register'),
),
],
),
);
}

void _submitForm() {
if (_formKey.currentState!.validate()) {
// Xử lý form
}
}
}

3. Form với Provider

3.1. Form Provider

class FormProvider extends ChangeNotifier {
String _email = '';
String _password = '';
bool _isLoading = false;
String? _error;

String get email => _email;
String get password => _password;
bool get isLoading => _isLoading;
String? get error => _error;

void updateEmail(String value) {
_email = value;
notifyListeners();
}

void updatePassword(String value) {
_password = value;
notifyListeners();
}

Future<void> submitForm() async {
_isLoading = true;
_error = null;
notifyListeners();

try {
// Xử lý form
await Future.delayed(Duration(seconds: 1)); // Giả lập API call
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
}

3.2. Form với Provider

class ProviderForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => FormProvider(),
child: Consumer<FormProvider>(
builder: (context, formProvider, child) {
return Form(
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: formProvider.updateEmail,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: formProvider.updatePassword,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
return null;
},
),
if (formProvider.error != null)
Text(
formProvider.error!,
style: TextStyle(color: Colors.red),
),
ElevatedButton(
onPressed: formProvider.isLoading
? null
: formProvider.submitForm,
child: formProvider.isLoading
? CircularProgressIndicator()
: Text('Submit'),
),
],
),
);
},
),
);
}
}

4. Best Practices

4.1. Tách biệt logic validation

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

static String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
}
}

4.2. Xử lý lỗi và loading state

class FormState {
final bool isLoading;
final String? error;
final Map<String, String> fieldErrors;

FormState({
this.isLoading = false,
this.error,
this.fieldErrors = const {},
});

FormState copyWith({
bool? isLoading,
String? error,
Map<String, String>? fieldErrors,
}) {
return FormState(
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
fieldErrors: fieldErrors ?? this.fieldErrors,
);
}
}

5. Ví dụ thực tế

5.1. Form đăng ký

class RegistrationForm extends StatefulWidget {
@override
_RegistrationFormState createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;

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

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

try {
// Xử lý đăng ký
await Future.delayed(Duration(seconds: 1));
// Chuyển hướng sau khi đăng ký thành công
} catch (e) {
// Hiển thị lỗi
} finally {
setState(() {
_isLoading = false;
});
}
}
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Full Name',
prefixIcon: Icon(Icons.person),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: ValidationRules.validateEmail,
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: ValidationRules.validatePassword,
),
ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
child: _isLoading
? CircularProgressIndicator()
: Text('Register'),
),
],
),
);
}
}

5.2. Form đăng nhập

class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
bool _obscurePassword = true;

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

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

try {
// Xử lý đăng nhập
await Future.delayed(Duration(seconds: 1));
// Chuyển hướng sau khi đăng nhập thành công
} catch (e) {
// Hiển thị lỗi
} finally {
setState(() {
_isLoading = false;
});
}
}
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: ValidationRules.validateEmail,
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
obscureText: _obscurePassword,
validator: ValidationRules.validatePassword,
),
ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
child: _isLoading
? CircularProgressIndicator()
: Text('Login'),
),
],
),
);
}
}

Kết luận

Quản lý form và validation là một phần quan trọng trong phát triển ứng dụng Flutter. Việc hiểu và áp dụng đúng cách các kỹ thuật quản lý form và validation sẽ giúp bạn tạo ra trải nghiệm người dùng tốt hơn và giảm thiểu lỗi trong ứng dụng.


Tài liệu tham khảo: