flutter_smart_dialog shows dialogs from anywhere, which is both the point and the constraint
An elegant Flutter Dialog solution | 一种更优雅的 Flutter Dialog 解决方案 | https://xdd666t.github.io/flutter_use/web/index.html#/smartDialog
At a glance
- What is it?
- flutter_smart_dialog is a MIT licensed Flutter package that replaces showDialog and SnackBar with static calls that need no BuildContext, and adds a dialog stack, attach dialogs positioned at a widget, a highlight mask, toasts, loading overlays and themed notifications. The cost of that convenience is that the library takes over the app's overlay and has to be initialised at the root.
- Who is it for?
- flutter_smart_dialog suits a Flutter team that wants one consistent dialog vocabulary across an app, including calls from services and background callbacks that have no context to hand, and that wants a loading overlay and a toast that behave the same everywhere.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 61 days ago.
- What is it written in?
- Mainly Dart, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Static calls are the feature, and the reason for the setup
flutter_smart_dialog presents itself as an elegant Flutter dialog solution, and the mechanism behind that is a single decision: the dialogs are opened with static calls that do not need a BuildContext.
In stock Flutter, showing a dialog means calling showDialog with a context, because the dialog has to be pushed onto a Navigator and a Navigator needs to be found in the widget tree. That is why so much application code threads a context through service classes, or stashes one in a global variable, or worse, calls showDialog from a callback where no context is available. This package removes that constraint, and the listed advantages make it explicit: you do not need BuildContext, the dialog can penetrate a dark background so you can click the page behind it, there is a dialog stack with the ability to close a specified dialog, there is support for a positioning widget so a dialog can appear at a specified location, there is a highlight feature that dissolves a mask at a specified location, and toast, loading, attach, custom dialogs and custom notifications are all easy to implement.
That freedom comes with setup. The package needs to be initialised in two places in the app:
return MaterialApp(
home: HomePage,
navigatorObservers: [FlutterSmartDialog.observer],
builder: FlutterSmartDialog.init(),
);The observer is what lets the package know about route changes so it can keep its own overlay in step, and the builder is where its widget tree is installed above your app. Everything the library does then hangs off that installation point, which is the single most important fact for anyone considering it.
The documentation is a separate site rather than the README, a super detailed guide at xdd666t.github.io, with the English and Chinese versions, and the README calls it a must read.
The dialog stack is what makes static calls coherent
A dialog system without a stack is a screen of overlapping windows, and the stack is the mechanism that keeps static calls predictable.
The listed capability is support for a dialog stack and the ability to close the specified dialog. That matters because the same static call is used for a toast, a loading overlay, an attach dialog and a custom dialog, and each of those can be open at the same time. When they are not stack aware, one call dismisses another, and a loading spinner that finishes after a dialog opened will take the dialog with it.
The difference in approach against the stock showDialog is worth stating. With Navigator, each dialog is a route, so a dialog is scoped to the navigator that pushed it, and popping it is a navigation operation. Here the dialogs live in a stack that the package owns, and closing one is an explicit call rather than a pop, which is what allows the API to be context free.
The penetration feature is the other half of the same idea. The listed advantage is that the dialog can penetrate a dark background and you can click on the page behind the dialog, and there is a configuration switch for it, usePenetrate, in SmartConfigAttach. So the mask is not always a wall. A dialog that the user can click through to dismiss, or an overlay that does not swallow touches, is a different interaction from a modal route, and the fact that it is a configuration flag rather than a hard coded behaviour is what makes it usable across a product.
Attach dialogs anchor themselves to a widget
The attach feature is the part of the package that has no stock Flutter equivalent, and it is the reason to choose this package over writing your own helper.
There is support for a positioning widget, so a dialog can be displayed at a specified location, and a highlight feature that dissolves the specified location mask. The point is that a dialog can be attached to an element of your interface rather than floating in the middle of the screen. A message that says what went wrong can appear next to the field that caused it; a row action can open its own menu of options in place; a card can expand into a detail panel over itself.
The configuration for it lives in SmartConfigAttach, with animationType set to SmartAnimationType.scale in the example, and the usePenetrate flag described earlier. The animation type is a parameter because an attach dialog that scales out of its anchor reads differently from one that fades, and the package exposes the choice.
The dialogStack example in the README's gallery is the same idea at a different level, and the effect gallery shows attachLocationAndPoint and attachHighlight as separate capabilities: positioning at a location, and dissolving the mask over a highlighted region. So the feature is not only a dialog that points at something, it is a way of directing attention to a specific part of the interface while a dialog is open.
For a team whose interface is form heavy, that is a real ergonomic gain, and for a team that only needs a confirm dialog and a snackbar, it is a lot of machinery to adopt.
Global config and custom builders keep the styling in one place
The configuration surface is a chain of config objects hung off SmartDialog.config, and each one controls a different part of the system.
SmartDialog.config
..custom = SmartConfigCustom(
maskColor: Colors.black.withOpacity(0.35),
useAnimation: true,
)
..attach = SmartConfigAttach(
animationType: SmartAnimationType.scale,
usePenetrate: false,
)
..loading = SmartConfigLoading(
clickMaskDismiss: false,
leastLoadingTime: const Duration(milliseconds: 0),
)
..toast = SmartConfigToast(
intervalTime: const Duration(milliseconds: 100),
displayTime: const Duration(milliseconds: 2000),
);Two of these settings are about user experience rather than appearance. clickMaskDismiss on the loading config controls whether tapping the mask dismisses the loading overlay, which is a decision about whether the overlay is a barrier or a status. And leastLoadingTime sets a floor on how long the loading overlay stays up, which exists because a fast response would otherwise make the overlay flash for a few milliseconds, which reads as a glitch.
The toast config has the same flavour. intervalTime is the minimum gap between toasts, and displayTime is how long each one stays, two seconds by default. A minimum interval is the fix for the classic problem where several operations finish at once and stack up toasts on top of each other.
For the widget level, the package lets you set default custom loading and toast styles at the entrance, with toastBuilder and loadingBuilder, plus a notifyStyle of type FlutterSmartNotifyStyle carrying successBuilder, failureBuilder, warningBuilder, alertBuilder and errorBuilder. The reason given is practical: if the only way to customise is to pass a builder to every call, then a codebase ends up with a hundred slightly different loading widgets, and the global default keeps them consistent.
Install, initialise, and show the first dialog
The package is published on pub.dev as flutter_smart_dialog, and the install step in the README is the standard pub install link, so adding it to a Flutter project is a dependency line in pubspec.yaml and a pub get.
The first code you write is the initialisation, shown earlier, with the observer in the navigatorObservers list and the builder passed to MaterialApp. Once that is in place, the everyday calls are one liners. A toast:
SmartDialog.showToast('test toast');A loading overlay that you dismiss yourself:
SmartDialog.showLoading();
await Future.delayed(Duration(seconds: 2));
SmartDialog.dismiss();And a custom dialog, where the builder returns any widget:
SmartDialog.show(builder: (context) {
return Container(
height: 80,
width: 180,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(10),
),
alignment: Alignment.center,
child:
Text('easy custom dialog', style: TextStyle(color: Colors.white)),
);
});The dismissal is explicit, which is worth pausing on. You call SmartDialog.dismiss() when the work is done, so the loading overlay lifetime is tied to the code that started the work rather than to a route pop. That is more control than Navigator gives you, and it is also more responsibility, because a code path that starts a loading and returns early without dismissing will leave the overlay up. The clickMaskDismiss setting and leastLoadingTime are the safety nets the configuration offers for that.
The custom dialog case is where the flexibility is obvious, since the builder takes any widget, and the same call pattern covers a confirm dialog, a bottom sheet style panel, or a form.
A 5.x line with a pinned answer for Flutter 2
The version history is worth reading before you copy an example from anywhere, because the configuration surface has changed under you more than once.
The published releases are v5.2.0 in August 2026, v5.0.0 in March 2026 and v4.9.8 in September 2024, with the repository not archived and the last push on 2026-08-23, the same day as the 5.2.0 tag. The 5.0.0 release is the kind of major bump where behaviour changes, and the project ships a migration document for the previous jump, from 3.x to 4.0, in the docs/ directory with a Chinese version linked from the README.
The current guidance for older Flutter is explicit: for Flutter 2, use flutter_smart_dialog 4.2.5. So a team on an older Flutter is not stuck, they are pinned to a specific older version, and a tutorial written for a different major version can be subtly wrong in ways that only show up at runtime.
The package is MIT licensed, and the repository layout is that of a real Flutter package rather than a single file. There is lib/, a full example/ application with the android, ios, linux, macos, web and windows platform folders, a test/ directory, a tool/ directory, docs/, and the usual pubspec.yaml, analysis_options.yaml and CHANGELOG.md, plus AGENTS.md and a CODEOWNERS file.
The example application covering six platforms is the thing to look at first if you are evaluating, because it shows the initialisation in a realistic app rather than in a snippet.
Where the design is wrong for your app
Three cases where this package is the wrong choice.
The first is a library that other teams consume. The package needs to install itself into the host app's MaterialApp, with an observer and a builder, and a library cannot do that unilaterally. An app can, but a published package cannot, so if you are writing a Flutter package rather than an app, showDialog and the platform's own snackbar remain the only options that will work without asking the host to change their app root.
The second is an app with an established dialog pattern. A large codebase that already has a house style for dialogs, built on showDialog, can be migrated piece by piece. A small codebase with one or two dialogs has nothing to gain from a dialog stack, an attach feature and a notify style hierarchy.
The third is a team that wants full control of the overlay. This package owns the overlay and the navigator observer, which is what makes the static API possible and also what means you cannot easily have a second overlay system layered on top. Two overlay managers will fight over the same part of the tree.
The alternative is to write the same thing yourself in about fifty lines, using an OverlayEntry, a global key for the dismiss call, and Navigator for anything that needs a route. The difference in approach is that a hand written helper gives you exactly the calls you need and nothing else, and costs you the stack semantics, the attach anchoring and the global configuration that this package already solved. That is the trade, and the package wins it when the app has many dialogs and loses it when it has one.
Editorial conclusion
flutter_smart_dialog suits a Flutter team that wants one consistent dialog vocabulary across an app, including calls from services and background callbacks that have no context to hand, and that wants a loading overlay and a toast that behave the same everywhere. It does not suit a library author who needs to own the overlay, since the package registers a navigator observer and installs its own builder, and it is the wrong choice if your app already has a consistent pattern built on showDialog. Verify first that the version you take supports your Flutter release, because the project pins Flutter 2 users to 4.2.5 while the current line is 5.x, and read the 3.x to 4.0 migration document before following an older tutorial, since the configuration surface changed.
Frequently asked questions
What does flutter_smart_dialog replace in a Flutter app?
It replaces the need to call showDialog or showSnackBar with a BuildContext, offering static calls for toast, loading, attach dialogs, custom dialogs and notifications, plus a dialog stack and global configuration.
How is flutter_smart_dialog initialised?
Add FlutterSmartDialog.observer to the MaterialApp navigatorObservers list and pass FlutterSmartDialog.init() as the builder. The init call also accepts toastBuilder, loadingBuilder and a notifyStyle with success, failure, warning, alert and error builders for custom defaults.
What is the attach feature in flutter_smart_dialog?
It is support for a positioning widget, so a dialog can be displayed at a specified location, together with a highlight feature that dissolves the mask at that location. Its configuration lives in SmartConfigAttach with animationType and usePenetrate.
Which version of flutter_smart_dialog should I use with Flutter 2?
The README states that Flutter 2 users should use flutter_smart_dialog 4.2.5. The current line is 5.x, with 5.2.0 released in August 2026, and the project also ships a migration document for 3.x to 4.0.
How do I show a loading overlay and dismiss it?
Call SmartDialog.showLoading(), await the work, then call SmartDialog.dismiss(). The SmartConfigLoading options control the behaviour, with clickMaskDismiss deciding whether tapping the mask dismisses it and leastLoadingTime setting a minimum display duration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fluttercandies-flutter-smart-dialog)Was this useful?