showDialog Patterns in Flutter: Alerts, Confirmations, and Custom Dialogs
Master showDialog with typed results, barrier dismissal, PopScope, platform-adaptive dialogs, and safe async context handling.
Published on • November 2, 2026
AI Assistant

Confirmation gates and small overlays are everywhere, and showDialog is Flutter’s modal primitive (docs.flutter.dev). The interesting part isn’t opening a dialog — it’s handling its result safely.
Typed Results with await showDialog<T>
showDialog returns a Future<T?> — the value the route was popped with; a barrier tap yields null, so treat == true as the affirmative path:
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Discard changes?'),
content: const Text('Your edits will be lost.'),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Discard')),
],
),
); // `confirmed == true` is the affirmative path
Variants: AlertDialog, SimpleDialog, Dialog
Pick the lightest surface: AlertDialog for confirmations, SimpleDialog for single-choice pickers, bare Dialog for fully custom layouts:
await showDialog<String>(
context: context,
builder: (ctx) => SimpleDialog(title: const Text('Sort by'), children: [
for (final o in ['Newest', 'Most read', 'A–Z'])
SimpleDialogOption(onPressed: () => Navigator.pop(ctx, o), child: Text(o)),
]),
);
Barrier and Builder Context
Set barrierDismissible: true to let taps outside close the dialog (returns null) — keep it false for destructive confirmations; barrierColor customizes the scrim (default Colors.black54). The builder’s context belongs to the dialog route: use it for Theme.of(ctx) lookups inside the dialog, and the parent context for anything scoped to the host page.
Back Navigation: PopScope Replaces WillPopScope
PopScope( // replaces the deprecated WillPopScope
canPop: false,
onPopInvokedWithResult: (didPop, result) { if (!didPop) _showUnsavedWarning(context); },
child: const DialogForm(),
)
Platform-Adaptive: Cupertino on iOS
final result = Theme.of(context).platform == TargetPlatform.iOS
? await showCupertinoDialog<bool>(context: context,
builder: (ctx) => CupertinoAlertDialog(
title: const Text('Delete photo'),
content: const Text('This cannot be undone.'),
actions: [
CupertinoDialogAction(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
CupertinoDialogAction(isDestructiveAction: true, onPressed: () => Navigator.pop(ctx, true), child: const Text('Delete')),
]))
: await showDialog<bool>(context: context, builder: (ctx) => AlertDialog(/* same wiring */));
Async Gaps and Small Screens
- After
await showDialog, checkcontext.mountedbeforeScaffoldMessengerorsetState— the host page may be gone. - Constrain long content (
SingleChildScrollViewinsideBoxConstraints(maxHeight: ...)) so dialogs don’t overflow compact phones.
Takeaway: await showDialog<T> with Navigator.pop(ctx, result) gives typed outcomes — pair it with context.mounted checks and PopScope for dialogs that survive real navigation.