Flutter: AlertDialog Kullanımı ve Özelleştirme
Son güncelleme:
5 dk okuma

AlertDialog, kullanıcının bir karar vermesi gerektiğinde ekranın ortasında açılan modal penceredir. Açık olduğu sürece arkadaki ekranla etkileşim kesilir; bu yüzden geri dönüşü zor işlemlerde (silme, çıkış yapma) ve devam etmeden önce mutlaka okunması gereken uyarılarda kullanırız. Dialog'u ekrana getiren showDialog fonksiyonu bir Future döndürür: kullanıcının hangi butona bastığını await ile bekleyip akışa oradan devam edebiliriz.
Canlı Demo
Aşağıdaki interaktif örnekte bu widget'ı deneyebilirsiniz:
💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.
Temel Kullanım
AlertDialog tek başına ekrana gelmez; onu yeni bir rota olarak açan showDialog fonksiyonuna veririz:
showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: const Text('Bağlantı yok'),
content: const Text('İnternet bağlantını kontrol edip tekrar dene.'),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('TAMAM'),
),
],
);
},
);builder içindeki parametreye bilerek dialogContext adını verdim. Dialog ayrı bir rota olduğu için kapatırken sayfanın değil dialog'un kendi context'ini kullanmak en güvenli yoldur; nedenini hatalar bölümünde göreceğiz.
Onay Dialog'u ve Sonucu Beklemek
Navigator.pop ikinci parametre olarak bir değer alır ve bu değer showDialog'un döndürdüğü Future'ın sonucu olur:
Future<void> _showConfirmDialog() async {
final result = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Silme Onayı'),
content: const Text('Bu öğeyi silmek istiyor musunuz?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('HAYIR'),
),
TextButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('EVET'),
),
],
),
);
if (result == true) {
// Silme işlemi
}
}Dönen tip bool?'dur. Kullanıcı dialog'un dışına dokunarak ya da geri tuşuyla kapatırsa sonuç null gelir. Bu yüzden if (result) yerine if (result == true) yazıyoruz; null durumu kendiliğinden "vazgeçti" sayılır. Dart'ta await mantığı oturmadıysa önce try-catch ve async/await yazısına göz atmak işi kolaylaştırır.
Girdi Alma
content herhangi bir widget olabilir. Tek bir değer almak için TextField yeterlidir:
// State sınıfında: final controller = TextEditingController();
final name = await showDialog<String>(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('İsim Girin'),
content: TextField(
controller: controller,
autofocus: true,
decoration: const InputDecoration(hintText: 'Adınız...'),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('İPTAL'),
),
TextButton(
onPressed: () => Navigator.pop(dialogContext, controller.text),
child: const Text('KAYDET'),
),
],
),
);Controller'ı State sınıfında tanımlayıp dispose() metodunda serbest bırakmak gerekir. Dialog kapanır kapanmaz controller.dispose() çağırmak cazip görünür ama kapanış animasyonu sürerken TextField hâlâ controller'ı kullandığı için hata verir.
Birden fazla alan ve doğrulama gerekiyorsa dialog'un içine Form widget'ı koymak, hatta bunu ayrı bir sayfaya taşımak daha sağlıklıdır.
Önemli Özellikler
| Özellik | Açıklama |
|---|---|
icon |
Başlığın üstünde gösterilen ikon |
title |
Dialog başlığı |
content |
Dialog içeriği |
actions |
Alt kısımdaki butonlar |
scrollable |
Uzun içerikte başlık ve içeriği kaydırılabilir yapar |
shape |
Dialog şekli |
backgroundColor |
Arka plan rengi |
barrierDismissible ise AlertDialog'un değil showDialog'un parametresidir.
barrierDismissible
Dialog dışına dokunulduğunda kapanmasını engellemek için:
showDialog<void>(
context: context,
barrierDismissible: false, // Dışına dokununca kapanmaz
builder: (dialogContext) => PopScope(
canPop: false, // Android geri tuşu da kapatmasın
child: AlertDialog(
title: const Text('Zorunlu Seçim'),
content: const Text('Devam etmek için bir seçenek seçmelisiniz.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('ANLADIM'),
),
],
),
),
);barrierDismissible: false yalnızca dışarı dokunmayı kapatır; Android'deki geri tuşu dialog'u yine kapatır. Onu da engellemek gerekiyorsa içeriği PopScope(canPop: false) ile sarmalarız. Bunu gerçekten zorunlu adımlarda kullanmak gerekir, kullanıcıyı kapana kıstırmak iyi bir deneyim değildir.
Platforma Uyumlu Dialog: showAdaptiveDialog
Aynı kodun Android'de Material, iOS ve macOS'ta Cupertino görünümlü dialog açmasını istiyorsak showAdaptiveDialog ile AlertDialog.adaptive ikilisini kullanırız:
showAdaptiveDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog.adaptive(
title: const Text('Çıkış yapılsın mı?'),
content: const Text('Tekrar giriş yapman gerekecek.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Vazgeç'),
),
TextButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Çıkış Yap'),
),
],
),
);Dialog'un çerçevesi platforma göre değişir ama actions içine ne koyduysak o çizilir. iOS'ta butonların da yerel görünmesi için Apple platformlarında TextButton yerine CupertinoDialogAction döndüren küçük bir yardımcı fonksiyon yazmak gerekir.
Ne Zaman Kullanılır, Ne Zaman Kullanılmaz?
AlertDialog kullanıcıyı durdurur. Bu güç, yalnızca durdurmaya değen anlarda kullanılmalı:
- Kullan: geri alınamayan işlemler için onay, kaydedilmemiş değişiklik uyarısı, izin gerekçesi, tek alanlık hızlı girdi.
- Kullanma: "Kaydedildi" gibi sadece bilgi veren mesajlar. Bunlar akışı kesmemeli; doğru araç SnackBar'dır.
- Kullanma: uzun seçenek listeleri ya da çok alanlı formlar. Seçenek listesi için
showModalBottomSheetveyaSimpleDialog, form için ise ayrı bir sayfa daha rahat kullanılır. Bottom sheet'ten değer döndürmeyi ve yükseklik ayarlarını BottomSheet yazısında anlattım.
Pratik bir kural: dialog'daki metin iki cümleyi, buton sayısı ikiyi geçiyorsa muhtemelen yanlış bileşendeyiz.
Sık Yapılan Hatalar
1. await sonrasında context'i kontrolsüz kullanmak
Belirti: Analiz aracı use_build_context_synchronously uyarısı verir ("Don't use 'BuildContext's across async gaps"). Çalışma anında ise kullanıcı dialog açıkken sayfadan çıkmışsa, artık ağaçta olmayan bir context üzerinden işlem yapılır ve hata alınır.
Çözüm: Her await'ten sonra context'e dokunmadan önce kontrol etmek:
final ok = await showDialog<bool>(context: context, builder: _buildDialog);
if (!context.mounted) return;
if (ok == true) Navigator.of(context).pop();State sınıfının içindeysek if (!mounted) return; da aynı işi görür.
2. Dialog içinde setState'in ekranı güncellememesi
Belirti: Dialog'a bir Checkbox koyarız, tıklarız, değer değişir ama görüntü değişmez. Dialog kapatılıp açılınca işaretli gelir.
Sebep: Dialog ayrı bir rotadır. Sayfanın setState'i sayfayı yeniden çizer, dialog'u değil.
Çözüm: Dialog içeriğini StatefulBuilder ile sarmak (ya da ayrı bir StatefulWidget yazmak):
bool dontAskAgain = false;
showDialog<void>(
context: context,
builder: (dialogContext) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: const Text('Çıkış'),
content: CheckboxListTile(
value: dontAskAgain,
title: const Text('Bir daha sorma'),
onChanged: (v) => setDialogState(() => dontAskAgain = v ?? false),
),
),
),
);3. İçeriğin taşması
Belirti: Klavye açıldığında ya da küçük ekranda sarı-siyah şeritler ve "A RenderFlex overflowed by … pixels on the bottom" hatası.
Çözüm: content içinde Column kullanıyorsak mainAxisSize: MainAxisSize.min verip AlertDialog'a scrollable: true eklemek. Column'un dialog içinde neden gereğinden fazla yer kapladığını mainAxisSize yazısındaki dialog tarifinde ayrıntılı anlattım.
4. Dialog'u yanlış context ile kapatmak
Belirti: İç içe Navigator kullanılan uygulamalarda (örneğin sekmelerin kendi navigator'ı varsa) butona basınca dialog ekranda kalır, alttaki sayfa kapanır.
Sebep: showDialog varsayılan olarak kök navigator'ı kullanır. Sayfanın context'i ile Navigator.pop(context) çağırmak ise en yakın, yani içteki navigator'ı bulur.
Çözüm: builder'ın verdiği dialogContext ile kapatmak. Bu yazıdaki tüm örneklerde bu yüzden o ismi kullandım.
Mini Senaryo: Silmeden Önce Onay, Sonra Bildirim
Bir not uygulaması düşünelim. Kullanıcı bir notu silmek istediğinde önce onay alacağız, silme bitince de kısa bir bildirim göstereceğiz. Note modeli ve repository nesnesi uygulamada zaten var sayılıyor:
Future<void> _confirmAndDelete(Note note) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
icon: const Icon(Icons.delete_outline),
title: const Text('Not silinsin mi?'),
content: Text('"${note.title}" kalıcı olarak silinecek.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Vazgeç'),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Sil'),
),
],
),
);
if (confirmed != true) return; // Vazgeçildi ya da dışına dokunuldu
await widget.repository.delete(note.id);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('"${note.title}" silindi')),
);
}Burada üç karar var. Birincisi, başlık soruyu, butonlar cevabı söylüyor: "Evet/Hayır" yerine "Sil/Vazgeç" yazınca kullanıcı metni okumadan da neye bastığını bilir. İkincisi, tehlikeli eylem FilledButton ile vurgulanmış, vazgeçme ise sakin bir TextButton. Üçüncüsü, iki await'in ardından mounted kontrolü yapılmadan context'e dokunulmuyor. Sonuç bilgisi ise dialog ile değil SnackBar ile veriliyor, çünkü artık kullanıcıyı durdurmaya gerek yok.
Sık Sorulan Sorular
AlertDialog dışına dokununca kapanmasını nasıl engellerim?
showDialog fonksiyonuna barrierDismissible: false verilir. Android'de geri tuşunun da kapatmaması isteniyorsa dialog PopScope(canPop: false) ile sarılır.
showDialog'dan dönen değer neden null geliyor?
Kullanıcı dialog'u bir butona basmadan, dışına dokunarak ya da geri tuşuyla kapattığında Navigator.pop değer almadan çağrılmış olur ve sonuç null döner. Bu yüzden sonucu result == true şeklinde karşılaştırmak gerekir.
AlertDialog ile SimpleDialog arasındaki fark nedir?
AlertDialog bir mesaj ve altında eylem butonları için tasarlanmıştır. SimpleDialog ise buton satırı olmadan, SimpleDialogOption öğelerinden oluşan kısa bir seçenek listesi gösterir.
Dialog iOS'ta nasıl yerel görünür?
showAdaptiveDialog ve AlertDialog.adaptive kullanıldığında iOS ve macOS'ta Cupertino tarzı dialog açılır. Butonların da yerel görünmesi için bu platformlarda CupertinoDialogAction kullanmak gerekir.
İlgili Yazılar
Flutter: BottomSheet ve showModalBottomSheet Kullanımı
Flutter'da showModalBottomSheet ile değer döndürmek, isScrollControlled, useSafeArea, showDragHandle, klavye boşluğu, DraggableScrollableSheet ve kalıcı sheet.
Flutter: Card Widget ve Tasarım Kullanımı
Flutter Card rehberi: Material 3 Card.filled ve Card.outlined, resimde clipBehavior, InkWell ile tıklanabilir kart, sık hatalar ve ürün kartı örneği.
Flutter: AppBar Widget ve Özelleştirme
Flutter AppBar rehberi: title, leading, actions ve bottom kullanımı, Material 3'te kaydırınca renk değişimi ve sık yapılan hataların çözümü.