Skip to main content

tldr

copy one file into your project, call showAppDialog. no setup, no init call.

final ok = await showAppDialog<bool>(
  context: context,
  title: "Delete workout?",
  message: "This removes it from your history.",
  actions: [
    AppDialogAction(text: "Cancel", type: AppDialogActionType.secondary, result: false),
    AppDialogAction(text: "Delete", isDestructive: true, result: true),
  ],
);

card surface, optional icon + message, equal-width action row. actions pop with their result.

live demo

starting the demo

install

copy one file

grab it and drop it in at lib/app_dialog.dart.

app_dialog.dart658 lines

it imports material from the flutter sdk. nothing goes in pubspec.yaml.

use it

there is no init call. the defaults stand on their own.

await showAppDialog<bool>(
  context: context,
  title: "Sign out?",
  actions: [
    AppDialogAction(text: "Cancel", type: AppDialogActionType.secondary, result: false),
    AppDialogAction(text: "Sign out", result: true),
  ],
);

usage

confirm / cancel is the usual shape. secondary on the left, primary on the right.

final shouldDelete = await showAppDialog<bool>(
  context: context,
  title: "Delete workout?",
  message: "This will remove this workout from your history.",
  icon: Icons.delete_outline_rounded,
  iconColor: Theme.of(context).colorScheme.error,
  actions: [
    AppDialogAction(
      text: "Cancel",
      type: AppDialogActionType.secondary,
      result: false,
    ),
    AppDialogAction(
      text: "Delete",
      isDestructive: true,
      result: true,
    ),
  ],
);

if (shouldDelete == true) {
  await deleteWorkout();
}

isDestructive paints the primary action in the error color. override fills directly when you need something else.

AppDialogAction(
  text: "Delete",
  backgroundColor: const Color(0xFFC62828),
  foregroundColor: Colors.white,
  result: true,
)

skip the message or the icon when the title is enough.

await showAppDialog<bool>(
  context: context,
  title: "Discard changes?",
  actions: [
    AppDialogAction(text: "Keep editing", type: AppDialogActionType.secondary, result: false),
    AppDialogAction(text: "Discard", isDestructive: true, result: true),
  ],
);

barrier tap dismisses by default and returns null. pass barrierDismissible: false to require an action.

example

Future<void> confirmSignOut(BuildContext context) async {
  final confirmed = await showAppDialog<bool>(
    context: context,
    title: "Sign out?",
    message: "This clears data on this device and returns you to sign in.",
    icon: Icons.logout_rounded,
    actions: const <AppDialogAction<bool>>[
      AppDialogAction(
        text: "Cancel",
        type: AppDialogActionType.secondary,
        result: false,
      ),
      AppDialogAction(
        text: "Sign out",
        result: true,
      ),
    ],
  );

  if (confirmed == true && context.mounted) {
    await auth.signOut();
  }
}

reference

everything below is here when you need it. you can ship without reading any of it.