AlertDialog
Dialog konfirmasi dengan showDialog dan actions.
Kenapa Dialog Ada?
Beberapa keputusan terlalu penting untuk dilakukan diam-diam: menghapus data, keluar dari akun, membatalkan pesanan. Kalau aksi seperti itu dijalankan langsung dari tombol, tidak ada jalan kembali dan user menyalahkan aplikasimu. Dialog menyelesaikan masalah ini dengan interupsi yang sopan: hentikan alur sejenak, jelaskan konsekuensinya, minta keputusan eksplisit.
Kuncinya ada di nilai kembalian: showDialog mengembalikan Future berisi nilai dari Navigator.pop, sehingga alur "tanya dulu, bertindak kemudian" bisa ditulis linear dengan async/await.
Dialog Konfirmasi Dasar
ElevatedButton(
onPressed: () async {
final yakin = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Hapus item?'),
content: const Text('Item yang dihapus tidak bisa dikembalikan.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Batal'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Hapus', style: TextStyle(color: Colors.red)),
),
],
),
);
if (yakin == true) {
// lakukan penghapusan
}
},
child: const Text('Hapus'),
)Perhatikan showDialog<bool>: tipe generic menentukan tipe nilai kembali. Navigator.pop(context, true) mengirim true, sedangkan tap di luar dialog atau tombol back menghasilkan null. Itulah kenapa ceknya yakin == true.
Contoh Nyata: Konfirmasi dengan Nama Item
Dialog yang menyebut nama item spesifik jauh lebih meyakinkan daripada teks generik:
Future<void> _konfirmasiHapus(String nama) async {
final yakin = await showDialog<bool>(
context: context,
barrierDismissible: false, // wajib pilih, tidak bisa tap di luar
builder: (context) => AlertDialog(
icon: const Icon(Icons.warning_amber, color: Colors.orange, size: 40),
title: const Text('Hapus produk?'),
content: Text('"$nama" akan dihapus permanen dari katalog.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Batal'),
),
FilledButton(
style: FilledButton.styleFrom(backgroundColor: Colors.red),
onPressed: () => Navigator.pop(context, true),
child: const Text('Ya, hapus'),
),
],
),
);
if (yakin == true && mounted) {
await _hapusDariDatabase(nama);
if (mounted) setState(() {});
}
}Dua detail penting di sini. Pertama, barrierDismissible: false dipakai karena ini aksi destruktif: user harus memilih eksplisit, tidak boleh "tidak sengaja" menutup dialog. Kedua, cek mounted setelah setiap await, karena user bisa menutup halaman saat proses berjalan dan setState pada widget yang sudah dispose akan error.
Varian Dialog Lain
AlertDialog bukan satu-satunya. SimpleDialog cocok untuk "pilih satu dari daftar" (mis. pilih bahasa) dengan SimpleDialogOption sebagai itemnya, dan ia juga mengembalikan nilai lewat Navigator.pop. Untuk input singkat di dalam dialog, taruh TextField di content dan ambil nilainya via controller saat tombol OK ditekan. Kalau butuh sesuatu yang lebih besar dari dialog tapi belum layak satu halaman penuh, showModalBottomSheet adalah alternatif yang dibahas di modul berikutnya.
Edge Case & Best Practice
Pertama, jangan pernah memanggil showDialog langsung di initState; frame belum siap dan context belum valid. Bungkus dengan WidgetsBinding.instance.addPostFrameCallback. Kedua, hindari dialog bertumpuk, susah ditutup dan membingungkan. Ketiga, batasi actions maksimal tiga tombol, lebih dari itu user tidak membaca dan asal klik.
Keempat, untuk proses loading yang diawali dari dialog (mis. hapus lalu tunggu server), tutup dialog dulu baru tampilkan loading, jangan menumpuk indikator di dalam dialog. Kelima, di tablet atau layar lebar, dialog bawaan sudah otomatis dibatasi lebarnya, jadi tidak perlu mengatur ukuran manual.
Kapan TIDAK Memakai Ini
Untuk info ringan yang tidak butuh keputusan ("Tersimpan!"), pakai SnackBar, dialog terlalu berat. Untuk form panjang, buat halaman tersendiri, dialog yang scroll panjang itu menyiksa. Dan yang paling penting: jangan terlalu sering. Aplikasi yang menampilkan dialog di setiap aksi kecil melatih user menekan "OK" tanpa membaca, sehingga saat dialog penting muncul, ia sudah kebal. Itu namanya dialog fatigue, dan itu salah desainmu, bukan salah user.
Catatan teknis:
showDialogmemakaiNavigatordi bawahnya, jadi ia menghormati tombol back Android (menutup dialog, bukan halaman). Kalau kamu butuh dialog yang tidak bisa ditutup sama sekali, gabungkanbarrierDismissible: falsedenganPopScope(canPop: false)di dalam builder.
Tantangan
Konfirmasi logout
Buat tombol 'Keluar' yang menampilkan AlertDialog 'Yakin ingin keluar?'. Jika user pilih 'Ya', tampilkan SnackBar 'Berhasil keluar'.