Forms
Forms ក្នុង Flutter
Forms ប្រើសម្រាប់ទទួលយក input ពី user និង validate data មុនពេល process។ Flutter មាន Form widget ដែលងាយស្រួលគ្រប់គ្រង multiple input fields។
📚 Form Validation Theory
Form Lifecycle:
Form Lifecycle (3 States)
1️⃣ CREATION
- Form widget created with GlobalKey
- TextFormField widgets added
- Validators defined
2️⃣ VALIDATION
- User fills form
- validate() checks all fields
- Shows error messages if invalid
3️⃣ SUBMISSION
- save() stores all field values
- Process data (API call, database, etc)
- Show success/error feedback
Form Components:
Component | Purpose | Example |
---|---|---|
Form |
Container for form fields | Wraps all input widgets |
GlobalKey<FormState> |
Access form state | Validate & save form |
TextFormField |
Input field with validation | Name, Email, Password fields |
validator |
Check if input is valid | Email format, length check |
onSaved |
Store field value | Save to variable/model |
Validation Strategies:
- 1. On Submit: Validate when user clicks submit button (default)
- 2. On Change: Validate as user types (real-time feedback)
- 3. On Focus Change: Validate when user leaves field
- 4. Manual: Validate programmatically when needed
📝 Form Widget (Basic)
class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
String name = '';
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'ឈ្មោះ'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'សូមបញ្ចូលឈ្មោះ';
}
return null;
},
onSaved: (value) {
name = value!;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
print('Name: $name');
}
},
child: Text('Submit'),
),
],
),
);
}
}
🎯 Advanced Form Features
1. Custom Input Validators
// Email validator
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'សូមបញ្ចូល Email';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}' + r'$');
if (!emailRegex.hasMatch(value)) {
return 'Email មិនត្រឹមត្រូវ';
}
return null;
}
// Password validator
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'សូមបញ្ចូល Password';
}
if (value.length < 8) {
return 'Password ត្រូវតែមានយ៉ាងតិច 8 តួអក្សរ';
}
if (!value.contains(RegExp(r'[A-Z]'))) {
return 'ត្រូវមាន uppercase letter';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return 'ត្រូវមានលេខ';
}
return null;
}
// Phone validator
String? validatePhone(String? value) {
if (value == null || value.isEmpty) {
return 'សូមបញ្ចូលលេខទូរស័ព្ទ';
}
final phoneRegex = RegExp(r'^[0-9]{9,10}' + r'$');
if (!phoneRegex.hasMatch(value)) {
return 'លេខទូរស័ព្ទមិនត្រឹមត្រូវ';
}
return null;
}
2. Complete Registration Form
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 _obscurePassword = true;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// Process data
print('Name: ' + _nameController.text);
print('Email: ' + _emailController.text);
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('ការចុះឈ្មោះជោគជ័យ!')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ចុះឈ្មោះ')),
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: ListView(
children: [
// Name field
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'ឈ្មោះពេញ',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'សូមបញ្ចូលឈ្មោះ';
}
if (value.length < 3) {
return 'ឈ្មោះត្រូវតែមានយ៉ាងតិច 3 តួអក្សរ';
}
return null;
},
),
SizedBox(height: 16),
// Email field
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'អ៊ីមែល',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
validator: validateEmail,
),
SizedBox(height: 16),
// Password field
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'លេខសម្ងាត់',
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
validator: validatePassword,
),
SizedBox(height: 24),
// Submit button
ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
),
child: Text('ចុះឈ្មោះ', style: TextStyle(fontSize: 18)),
),
],
),
),
),
);
}
}
3. Dropdown & Checkbox
class FormWithDropdown extends StatefulWidget {
@override
_FormWithDropdownState createState() => _FormWithDropdownState();
}
class _FormWithDropdownState extends State<FormWithDropdown> {
final _formKey = GlobalKey<FormState>();
String? _selectedCountry;
bool _agreeToTerms = false;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
// Dropdown
DropdownButtonFormField<String>(
value: _selectedCountry,
decoration: InputDecoration(
labelText: 'ប្រទេស',
border: OutlineInputBorder(),
),
items: ['កម្ពុជា', 'ថៃ', 'វៀតណាម', 'ឡាវ']
.map((country) => DropdownMenuItem(
value: country,
child: Text(country),
))
.toList(),
onChanged: (value) {
setState(() {
_selectedCountry = value;
});
},
validator: (value) {
if (value == null) {
return 'សូមជ្រើសរើសប្រទេស';
}
return null;
},
),
// Checkbox
CheckboxListTile(
title: Text('ខ្ញុំយល់ព្រមតាមលក្ខខណ្ឌ'),
value: _agreeToTerms,
onChanged: (value) {
setState(() {
_agreeToTerms = value ?? false;
});
},
controlAffinity: ListTileControlAffinity.leading,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
if (!_agreeToTerms) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('សូមយល់ព្រមតាមលក្ខខណ្ឌ')),
);
return;
}
// Submit form
}
},
child: Text('Submit'),
),
],
),
);
}
}
🎨 Form Styling Best Practices
InputDecoration(
labelText: 'ឈ្មោះ',
hintText: 'បញ្ចូលឈ្មោះរបស់អ្នក',
helperText: 'ឈ្មោះពេញ',
prefixIcon: Icon(Icons.person),
suffixIcon: Icon(Icons.check_circle, color: Colors.green),
// Border styles
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red),
),
// Colors
filled: true,
fillColor: Colors.grey[100],
)
💡 Best Practices:
- ✅ Always dispose TextEditingController in dispose()
- ✅ Use meaningful error messages in Khmer
- ✅ Provide visual feedback (icons, colors)
- ✅ Disable submit button while processing
- ✅ Use autovalidateMode for real-time validation
⚠️ Common Mistakes:
- ❌ Forgetting to dispose controllers (memory leak)
- ❌ Not handling null values in validators
- ❌ Using print() instead of proper error handling
- ❌ Not providing user feedback after submission
💡 ជំនួយ: ប្រើ Form widget សម្រាប់ validate input។ ប្រើ TextEditingController សម្រាប់ get/set values។ ប្រើ GlobalKey<FormState> សម្រាប់ validate និង save ទាំងអស់។