Skip to main content
← back

app dialog

lib/app_dialog.dart · flutter · dart

a dialog for flutter in one file. title, message, icon, action row. no packages, no DI, no widget wrapping.

llms.txt

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.