dismissDialog static method
Dismisses only the dialog by its ID
Parameters:
id: The ID of the dialog to dismiss (required)onDismissed: Optional callback executed after the dialog is dismissed
Example:
// Dismiss current dialog (less safe)
Modal.dismissDialog();
// Dismiss specific dialog by ID (recommended)
Modal.dismissDialog(id: 'settings_dialog');
Implementation
static Future<void> dismissDialog(
{String? id, VoidCallback? onDismissed}) async {
if (Modal.isDialogActive && !Modal.isDialogDismissing) {
// CAPTURE the ID of the dialog we intend to dismiss at the START
final targetDialogId = _dialogController.state?.uniqueId;
final dialogId = _dialogController.state?.id;
if (targetDialogId == null) {
// debugPrint(
// 'Modal.dismissDialog: WARNING - No dialog ID found, aborting');
return;
}
// If an ID was provided, verify it matches before dismissing
if (id != null && id != dialogId && id != targetDialogId) {
// debugPrint(
// 'Modal.dismissDialog: ID mismatch. Requested=$id, Active=$dialogId. Aborting.');
return;
}
// debugPrint('Modal.dismissDialog: targeting dialog ID=$targetDialogId');
Modal.isDismissing = true;
_dialogDismissingNotifier.state = true;
// Capture the modal type that is being dismissed NOW so we can clear
// the correct type-specific controller after callbacks run.
final originallyDismissedType = _activeModalController.state?.modalType;
// Capture the modal type being dismissed so we clear the correct
// type-specific controller even if callbacks change the active modal.
// Note: We intentionally do not capture dismissed type here because
// bottom sheet cleanup is handled below when we refresh the controller.
// Reset blur animation state - BUT ONLY if no other blur-enabled modal remains active
// If a bottom sheet with blur is still showing, we must preserve its blur
final sheetNeedsBlur = Modal.isSheetActive &&
(_sheetController.state?.shouldBlurBackground ?? false);
if (!sheetNeedsBlur) {
_blurAnimationStateNotifier.state = 0.0;
}
// Animate background out (if no other modals need it)
if (!Modal.isSheetActive) {
_backgroundAnimationTimer?.cancel();
const animSteps = 10;
const totalDuration = 300;
final stepDuration = Duration(milliseconds: totalDuration ~/ animSteps);
double startValue = _backgroundLayerAnimationNotifier.state;
double step = startValue / animSteps;
int currentStep = 0;
_backgroundAnimationTimer = Timer.periodic(stepDuration, (timer) {
currentStep++;
if (currentStep > animSteps) {
timer.cancel();
_backgroundAnimationTimer = null;
_backgroundLayerAnimationNotifier.state = 0.0;
} else {
_backgroundLayerAnimationNotifier.state =
startValue - (step * currentStep);
}
});
}
// Animate out
_dismissModalAnimationController.state = true;
// Allow animation time, then perform cleanup and run callbacks AFTER
// the modal is effectively torn down. This avoids race conditions
// where callbacks show new snackbars and the cleanup later clears them.
// debugPrint(
// 'Modal.dismissDialog: start (activeId=${_activeModalController.state?.uniqueId}, dialogId=${_dialogController.state?.uniqueId})');
await Future.delayed(0.4.sec, () {
// VALIDATE: Check if the dialog ID still matches what we intended to dismiss
final currentDialogId = _dialogController.state?.uniqueId;
if (currentDialogId != null && currentDialogId != targetDialogId) {
// debugPrint(
// 'Modal.dismissDialog: WARNING - Dialog ID changed during animation! '
// 'Target=$targetDialogId, Current=$currentDialogId. Aborting cleanup.');
Modal.isDismissing = false;
_dialogDismissingNotifier.state = false;
return;
}
// debugPrint(
// 'Modal.dismissDialog: animation complete, running cleanup for ID=$targetDialogId');
// debugPrint(
// 'Modal.dismissDialog: before cleanup: active=${Modal.controller.state?.modalType} activeId=${Modal.controller.state?.uniqueId} dialogId=${_dialogController.state?.uniqueId} snackbarQueue=${_snackbarQueueNotifier.state.length}');
// Capture the dialog's onDismissed callback before we refresh the controller
final dialogOnDismiss = _dialogController.state?.onDismissed;
_dialogController.refresh();
// Unregister from modal registry
final updatedRegistry =
Map<String, ModalType>.from(_modalRegistry.state);
updatedRegistry.remove(targetDialogId);
_modalRegistry.state = updatedRegistry;
// Run callbacks now that the dialog has been cleaned up
// debugPrint('Modal.dismissDialog: running callbacks (post-refresh)');
onDismissed?.call();
dialogOnDismiss?.call();
// debugPrint(
// 'Modal.dismissDialog: after callbacks: active=${Modal.controller.state?.modalType} activeId=${Modal.controller.state?.uniqueId} dialogId=${_dialogController.state?.uniqueId} snackbarQueue=${_snackbarQueueNotifier.state.length}');
final currentQueue = _snackbarQueueNotifier.state;
// debugPrint(
// 'Modal.dismissDialog: snackbar queue has ${currentQueue.keys.length} positions');
// IMPORTANT: Do NOT clear the snackbar queue when dismissing a dialog.
// Snackbars are independent modals and should remain visible.
// Only update the active modal controller to point to a remaining snackbar if any.
if (currentQueue.isNotEmpty) {
// debugPrint(
// 'Modal.dismissDialog: snackbar queue not empty, preserving snackbars');
// Find the first position with snackbars and make it the active modal
Alignment? positionWithContent;
for (final position in currentQueue.keys) {
if (currentQueue[position]!.isNotEmpty) {
positionWithContent = position;
break;
}
}
if (positionWithContent != null) {
final snackbarToShow = currentQueue[positionWithContent]!.first;
_snackbarController.state = snackbarToShow;
Modal.controller.state = snackbarToShow;
// Ensure the newly activated snackbar is not marked as dismissing
_setSnackbarDismissing(snackbarToShow.uniqueId, false);
Modal.dismissModalAnimationController.state = false;
}
}
// Clear type-specific controllers based on what was dismissed
// IMPORTANT: Only clear active modal controller if NO other modals are active
if (currentQueue.isEmpty && !Modal.isSheetActive) {
_snackbarController.refresh();
_activeModalController.refresh();
} else if (currentQueue.isEmpty && Modal.isSheetActive) {
// Bottom sheet is still active - make it the active modal
_snackbarController.refresh();
Modal.controller.state = _sheetController.state;
}
if (originallyDismissedType == ModalType.dialog) {
// Dialog was already refreshed above; just clear the dismissing flag
_dialogDismissingNotifier.state = false;
}
// Reset dismiss animation controller if no other modals active
if (!Modal.isSheetActive && !Modal.isSnackbarActive) {
_dismissModalAnimationController.state = false;
}
// debugPrint('Modal.dismissDialog: finished');
Modal.isDismissing = false;
HapticFeedback.lightImpact();
});
}
}