Android
AlertDialog
FlatButton
Dismiss Dialog
Mobile Development

How to dismiss an AlertDialog on a FlatButton click?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Flutter, dismissing an AlertDialog is done through the navigation stack: dialogs are routes, so closing one means popping that route. Older code examples use FlatButton, but current Flutter versions replaced it with TextButton. The underlying dismissal pattern remains the same: call Navigator.pop from the button callback.

The implementation details matter when you need a return value, async side effects, or nested navigators. This guide covers practical dialog dismissal patterns you can use safely in modern Flutter apps.

Core Sections

1. Basic dialog dismissal from button click

Use showDialog, render action buttons, and call Navigator.of(context).pop().

dart
1Future<void> showDeleteDialog(BuildContext context) async {
2  await showDialog<void>(
3    context: context,
4    builder: (BuildContext dialogContext) {
5      return AlertDialog(
6        title: const Text('Delete item'),
7        content: const Text('This action cannot be undone.'),
8        actions: [
9          TextButton(
10            onPressed: () => Navigator.of(dialogContext).pop(),
11            child: const Text('Cancel'),
12          ),
13          TextButton(
14            onPressed: () => Navigator.of(dialogContext).pop(),
15            child: const Text('Delete'),
16          ),
17        ],
18      );
19    },
20  );
21}

Use dialogContext from the builder so you pop the correct route.

2. Return a result from the dialog

Frequently you need to know which action user selected. Return a typed value via pop(result).

dart
1enum Decision { cancel, confirm }
2
3Future<Decision?> askConfirmation(BuildContext context) {
4  return showDialog<Decision>(
5    context: context,
6    builder: (dialogContext) {
7      return AlertDialog(
8        title: const Text('Confirm payment'),
9        actions: [
10          TextButton(
11            onPressed: () => Navigator.of(dialogContext).pop(Decision.cancel),
12            child: const Text('No'),
13          ),
14          TextButton(
15            onPressed: () => Navigator.of(dialogContext).pop(Decision.confirm),
16            child: const Text('Yes'),
17          ),
18        ],
19      );
20    },
21  );
22}
23
24Future<void> onPayPressed(BuildContext context) async {
25  final decision = await askConfirmation(context);
26  if (decision == Decision.confirm) {
27    // continue
28  }
29}

Typed results reduce ambiguity versus string-based action handling.

3. Handle async actions and lifecycle safely

If pressing a dialog button triggers async work, close dialog first (or show loading state) and guard BuildContext usage.

dart
1TextButton(
2  onPressed: () async {
3    Navigator.of(dialogContext).pop();
4
5    final ok = await repository.deleteItem();
6    if (!context.mounted) return;
7
8    ScaffoldMessenger.of(context).showSnackBar(
9      SnackBar(content: Text(ok ? 'Deleted' : 'Delete failed')),
10    );
11  },
12  child: const Text('Delete'),
13)

For nested navigators (for example tabs or shell routes), you may need rootNavigator: true.

dart
Navigator.of(dialogContext, rootNavigator: true).pop();

Use that only when dialog was opened on the root navigator.

Common Pitfalls

  • Using deprecated FlatButton in new Flutter projects instead of TextButton.
  • Calling Navigator.pop with the wrong context, which may pop a page instead of the dialog.
  • Forgetting to await dialog result and then running follow-up logic unconditionally.
  • Using context after async gaps without checking context.mounted.
  • Returning untyped results (dynamic) that make caller-side handling fragile.

Summary

To dismiss an AlertDialog, pop the dialog route from button callbacks. In modern Flutter, use TextButton, pass typed results when needed, and handle async follow-up with lifecycle checks. Once you treat dialogs as normal routes with clear result contracts, dismissal logic stays clean and predictable.

A good dialog API returns explicit outcomes and keeps side effects outside the builder closure when possible. Treat the dialog as a UI decision component and let caller code handle business actions. This separation makes widget tests simpler because you assert returned decisions, then test follow-up behavior independently. It also reduces duplicated Navigator.pop logic across similar dialogs.

Consider user experience around accidental taps. Setting barrierDismissible: false may be appropriate for destructive confirmations, while non-critical prompts can allow outside-tap dismissal. Be intentional, and ensure keyboard accessibility and focus order still work for desktop/web targets. Modern Flutter apps often run across multiple form factors, so dialog behavior should be tested beyond a single mobile path.

Keeping dialog code small and typed makes navigation flows more reliable as screens and route stacks grow.

Consistent dialog-result handling patterns also make analytics instrumentation easier, since each user choice can be captured in one predictable location.


Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.