Kemarin kita udah bikin Form + TextFormField dan sempat menyinggung validator. Tapi validasi yang bener itu bukan cuma “field nggak boleh kosong”. User itu kreatif — mereka bakal masukin email tanpa @, password cuma 3 huruf, atau konfirmasi password yang beda sama password aslinya. Tugas kamu: jangan percaya input user sampai lolos validasi, dan kasih feedback yang jelas kalau ada yang salah.
Hari ini kita bongkar tuntas validasi form + cara ngasih feedback lewat SnackBar yang user-friendly.
1. Cara Kerja validator
Di TextFormField, ada parameter validator yang berbentuk function:
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Nggak boleh kosong!';
}
return null; // ⬅️ null = VALID, nggak ada error
}
Aturan emas validator:
- Return
null→ field dianggap valid (nggak ada pesan error). - Return
String→ field dianggap invalid, string itu ditampilkan merah di bawah field. valuebisanullkalau field dikosongkan — selalu ceknulldulu!
2. Kumpulan Validator yang Wajib Kamu Punya
Daripada nulis validasi berulang-ulang, bikin kumpulan validator reusable:
class Validators {
// ✅ Wajib diisi
static String? required(String? value, [String? label]) {
if (value == null || value.trim().isEmpty) {
return '${label ?? 'Field ini'} nggak boleh kosong!';
}
return null;
}
// ✅ Email pakai regex
static String? email(String? value) {
final v = value?.trim() ?? '';
if (v.isEmpty) return 'Email nggak boleh kosong!';
final regex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!regex.hasMatch(v)) return 'Format email nggak valid!';
return null;
}
// ✅ Minimal panjang
static String? minLength(String? value, int min, [String? label]) {
final v = value?.trim() ?? '';
if (v.isEmpty) return '${label ?? 'Field ini'} nggak boleh kosong!';
if (v.length < min) return 'Minimal $min karakter!';
return null;
}
// ✅ Konfirmasi password (butuh nilai field lain)
static String? match(String? value, String other, [String? label]) {
if (value != other) return '${label ?? 'Konfirmasi'} nggak cocok!';
return null;
}
}
Sekarang form kamu bersih banget:
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: Validators.email,
),
3. autovalidateMode — Validasi Saat Mengetik
Default-nya, validator cuma jalan saat .validate() dipanggil (biasanya saat tombol submit ditekan). Kadang user pengen tau error-nya langsung tanpa nunggu tekan tombol. Di situlah autovalidateMode berperan:
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: Validators.email,
),
Pilihan mode:
AutovalidateMode.disabled— default, cuma validasi saat submitAutovalidateMode.onUserInteraction— validasi tiap kali user berinteraksi (nulis/hapus) — paling recommendedAutovalidateMode.always— validasi terus-menerus, bahkan sejak pertama build
Pro tip: Jangan pakai
alwayskecuali memang perlu. Validasi yang nyala sebelum user sempat ngetik bikin form berasa “marah-marah” ke user.
4. SnackBar — Feedback yang Elegan
Setelah validasi lolos/gagal, kasih tau user hasilnya. SnackBar adalah notifikasi kecil yang muncul di bawah layar — nggak ngeblok interaksi kayak AlertDialog.
// Sukses ✅
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('✅ Registrasi berhasil! Selamat datang!'),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
duration: const Duration(seconds: 3),
),
);
// Gagal ❌
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('❌ Ada field yang masih salah, cek lagi ya!'),
backgroundColor: Colors.red,
),
);
Kenapa pakai ScaffoldMessenger.of(context)? Karena kalau pakai Scaffold.of(context) dan halaman sudah pindah/Scaffold udah ke-dispose, SnackBar bakal error. ScaffoldMessenger lebih aman — dia dikelola di level MaterialApp, jadi nggak keilangan referensi.
5. Contoh Lengkap: Form Registrasi dengan Validasi Kuat
Gabungin semua jadi satu form register yang “production-ready”:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: RegisterScreen()));
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final _formKey = GlobalKey<FormState>();
final _emailCtrl = TextEditingController();
final _passCtrl = TextEditingController();
final _confirmCtrl = TextEditingController();
@override
void dispose() {
_emailCtrl.dispose();
_passCtrl.dispose();
_confirmCtrl.dispose();
super.dispose();
}
void _submit() {
// 1️⃣ Validasi semua field
if (!_formKey.currentState!.validate()) {
// Feedback gagal
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('❌ Cek lagi field yang merah di atas!'),
backgroundColor: Colors.red,
),
);
return;
}
// 2️⃣ Kalau lolos, save + proses
_formKey.currentState!.save();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('✅ Akun ${_emailCtrl.text} berhasil dibuat!'),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('🔐 Register')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _emailCtrl,
keyboardType: TextInputType.emailAddress,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'Email',
icon: Icon(Icons.email),
border: OutlineInputBorder(),
),
validator: Validators.email,
),
const SizedBox(height: 16),
TextFormField(
controller: _passCtrl,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
icon: Icon(Icons.lock),
border: OutlineInputBorder(),
),
validator: (v) => Validators.minLength(v, 6, 'Password'),
),
const SizedBox(height: 16),
TextFormField(
controller: _confirmCtrl,
obscureText: true,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'Konfirmasi Password',
icon: Icon(Icons.lock_outline),
border: OutlineInputBorder(),
),
validator: (v) =>
Validators.match(v, _passCtrl.text, 'Konfirmasi password'),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _submit,
icon: const Icon(Icons.check_circle),
label: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('Daftar'),
),
),
],
),
),
),
);
}
}
Jalankan dan coba:
- Tekan “Daftar” tanpa isi apa-apa → semua field merah + SnackBar merah muncul.
- Isi email
abc→ langsung muncul “Format email nggak valid!” saat ngetik (karenaonUserInteraction). - Password
123, konfirmasi456→ error “Konfirmasi password nggak cocok!”. - Isi semua dengan benar → SnackBar hijau “berhasil dibuat!”.
6. Styling Pesan Error
Pesan error default Flutter udah bagus, tapi kamu bisa ubah:
TextFormField(
decoration: InputDecoration(
labelText: 'Email',
errorStyle: const TextStyle(color: Colors.orange, fontSize: 12),
errorBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.orange, width: 2),
borderRadius: BorderRadius.circular(8),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.red, width: 2),
borderRadius: BorderRadius.circular(8),
),
),
validator: Validators.email,
)
errorStyle— styling teks errorerrorBorder— border field saat error (nggak fokus)focusedErrorBorder— border field saat error DAN sedang fokus
Ringkasan
validatorreturnnull(valid) atauString(pesan error)- Bikin validator reusable — jangan nulis ulang tiap form
autovalidateMode.onUserInteraction— validasi real-time tanpa bikin user keselSnackBar— feedback elegan, pakaiScaffoldMessenger.of(context)biar aman- Pola baku:
validate()→ gagal (SnackBar merah) / lolos (SnackBar hijau + proses data)
Validasi + feedback yang bener bikin aplikasimu berasa profesional dan peduli user. Ini skill yang bakal kepakai di semua project nyata — dari login sampai form checkout.
Besok kita lanjut ke Asset & Image — Gambar Lokal & Network. Siap-siap bikin tampilan makin hidup! 🎨
Coba sendiri! Bikin form login (email + password) dengan validasi lengkap dan SnackBar. Share hasilnya ke sosial media dan tag @ahsai001!