formvalidasiinputMenengah5 mnt baca

Form & Validasi

Validasi input terpusat dengan Form, GlobalKey, dan validator.

Kenapa Butuh Widget Form?

Form login atau pendaftaran punya banyak field, dan tiap field punya aturannya sendiri: email wajib dan harus mengandung @, password minimal 6 karakter, konfirmasi password harus sama. Tanpa Form, kamu harus mengecek semuanya manual satu per satu, menampilkan error sendiri, dan gampang ada yang terlewat.

Solusi terpusat: satu panggilan validate() menjalankan semua aturan sekaligus dan menampilkan pesan error otomatis di field yang salah.

dart
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(
        decoration: const InputDecoration(labelText: 'Email'),
        keyboardType: TextInputType.emailAddress,
        validator: (nilai) {
          if (nilai == null || nilai.isEmpty) return 'Email wajib diisi';
          if (!nilai.contains('@')) return 'Format email salah';
          return null; // null artinya valid
        },
      ),
      TextFormField(
        decoration: const InputDecoration(labelText: 'Password'),
        obscureText: true,
        validator: (nilai) {
          if (nilai == null || nilai.length < 6) return 'Minimal 6 karakter';
          return null;
        },
      ),
      ElevatedButton(
        onPressed: () {
          if (_formKey.currentState!.validate()) {
            // semua valid, baru proses data
            _kirimPendaftaran();
          }
        },
        child: const Text('Daftar'),
      ),
    ],
  ),
)

Kontrak validator sederhana: kembalikan String error jika tidak valid, null jika valid.

Kapan Validasi Dijalankan: autovalidateMode

Secara default validasi hanya jalan saat validate() dipanggil (saat tombol submit ditekan). User baru tahu ada yang salah setelah menekan tombol, jadi autovalidateMode memberi perilaku yang lebih halus:

dart
Form(
  key: _formKey,
  autovalidateMode: AutovalidateMode.onUserInteraction,
  ...
)
  • disabled (default): validasi hanya saat validate() dipanggil.
  • onUserInteraction: validasi jalan setelah user menyentuh field. Pilihan terbaik untuk kebanyakan form: tidak mengganggu di awal, tapi memberi feedback cepat.
  • always: validasi setiap ada perubahan, termasuk sebelum user menyentuh apa pun. Bikin form terlihat "marah" sejak awal, hindari kecuali ada alasan khusus.

Mengambil Nilai: onSaved dan save()

Selain validasi, Form bisa mengumpulkan nilai semua field sekaligus lewat onSaved:

dart
String? _email;

TextFormField(
  decoration: const InputDecoration(labelText: 'Email'),
  validator: (v) => (v == null || !v.contains('@')) ? 'Email tidak valid' : null,
  onSaved: (v) => _email = v,
)

ElevatedButton(
  onPressed: () {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save(); // panggil semua onSaved
      _kirimPendaftaran(_email!);
    }
  },
  child: const Text('Daftar'),
)

Pola ini rapi karena kamu tidak perlu TextEditingController untuk setiap field. Butuh reset form? _formKey.currentState!.reset() mengosongkan semua field sekaligus.

Contoh Nyata: Konfirmasi Password

Validator bisa membandingkan antar field, cukup simpan nilai field pertama di variabel:

dart
final _passController = TextEditingController();

TextFormField(
  controller: _passController,
  obscureText: true,
  decoration: const InputDecoration(labelText: 'Password'),
  validator: (v) => (v == null || v.length < 6) ? 'Minimal 6 karakter' : null,
),
TextFormField(
  obscureText: true,
  decoration: const InputDecoration(labelText: 'Konfirmasi Password'),
  validator: (v) {
    if (v != _passController.text) return 'Password tidak sama';
    return null;
  },
),

Catatan teknis: Pakai TextFormField, bukan TextField, di dalam Form. TextField biasa tidak terhubung ke sistem validator dan onSaved milik Form.

Edge Case: Validasi Async (Cek ke Server)

validator bersifat sinkron: tidak bisa await di dalamnya. Untuk cek yang butuh server (misalnya "username sudah dipakai"), lakukan manual setelah validasi lokal lolos:

dart
onPressed: () async {
  if (!_formKey.currentState!.validate()) return; // validasi lokal dulu
  setState(() => _loading = true);
  final dipakai = await api.cekUsername(_usernameController.text);
  if (!mounted) return;
  setState(() => _loading = false);
  if (dipakai) {
    // tampilkan error manual, karena validator tidak bisa async
    setState(() => _usernameError = 'Username sudah dipakai');
    return;
  }
  _kirimPendaftaran();
}

Untuk error manual seperti ini, pakai errorText di decoration, atau simpan pesan di state lalu tampilkan lewat validator yang membaca variabel tersebut.

Kapan JANGAN Pakai Form

  • Satu-dua field sederhana (kolom pencarian, input nama cepat): Form + GlobalKey adalah boilerplate yang tidak perlu. Cukup onChanged/onSubmitted atau satu controller.
  • Validasi sangat dinamis dengan aturan yang berubah-ubah: state eksplisit (misalnya ValueNotifier per field) kadang lebih mudah dibaca daripada validator yang saling bergantung.

Tantangan

Form login tervalidasi

Buat Form login: email (wajib + mengandung @) dan password (minimal 6). Tombol 'Masuk' hanya memproses jika validate() true, lalu tampilkan SnackBar 'Login berhasil'.