© Khmer Angkor Academy - sophearithput168

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 ទាំងអស់។