flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.01k stars 27.19k forks source link

[proposal] Provide a way to close a specific dialog #62960

Open tolotrasamuel opened 4 years ago

tolotrasamuel commented 4 years ago

Use case

Currently, there is no way to close a specific alert dialog.

In Flutter, the only way to close a specific dialog is by this dialoge itself like this

showSomeDialog() {
    return showDialog(
        context: context,
        builder: (BuildContext contextPopup) {
          return AlertDialog(
            content: Center(
              child: RaisedButton(
                onPressed: () {
                  Navigator.of(contextPopup).pop();
                },
                child: Text('Close me inside'),
              ),
            ),);
        }
    );
  }

Now what if the parent widget wants to close this dialog? The problem with Navigator.of(context).pop() is that it will close the topmost widget on the Navigation stack, not this specific dialog.

Consider the following reproducible example to illustrate this issue:

Steps to reproduce:

  1. Copy paste the below code in DartPad.dev/flutter

  2. Hit run

  3. Click the Do Api Call button

  4. you should see two popups, one below and one above

  5. After 5 seconds, the one below is desired to close not the one above, instead, the one above closes

How to close the one below and leave the one above open ?

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: CloseSpecificDialog(),
        ),
      ),
    );
  }
}

class CloseSpecificDialog extends StatefulWidget {
  @override
  _CloseSpecificDialogState createState() => _CloseSpecificDialogState();
}

class _CloseSpecificDialogState extends State<CloseSpecificDialog> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: RaisedButton(
        child: Text('Do API call'),
        onPressed: () async {
          showDialogBelow();
          showDialogAbove();
          await Future.delayed(Duration(seconds: 5));
          closeDialogBelowNotAbove();
        },
      )),
    );
  }

  void showDialogBelow() {
    showDialog(
        context: context,
        builder: (BuildContext contextPopup) {
          return AlertDialog(
            content: Container(
              width: 350.0,
              height: 150.0,
              child: Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    CircularProgressIndicator(),
                    Text('I am below (you should not see this after 5 sec)'),
                  ],
                ),
              ),
            ),
          );
        });
  }

  void showDialogAbove() {
    showDialog(
        context: context,
        builder: (BuildContext contextPopup) {
          return AlertDialog(
            content: Container(
              height: 100.0,
              child: Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    CircularProgressIndicator(),
                    Text('I am above (this should not close)'),
                  ],
                ),
              ),
            ),
          );
        });
  }

  /// This should close the dialog below not the one above
  void closeDialogBelowNotAbove() {
    Navigator.of(context).pop();
  }
}

Proposal

In Android, you just do: dialog.dismiss();

In Flutter, there should be a way to get the reference to each specific dialog Please make this as a P0 feature request because it is a very basic feature that Android does it so easily and it used so frequently by developers.

acoutts commented 4 years ago

I think the root issue here is in the way the current navigator works. You can't push or pop anything below the current active route.

Have you tried using the new navigator 2.0 pages api? I wonder if it's possible to do it in that, where the navigation stack is more malleable.

https://github.com/flutter/flutter/issues/45938

irtza46 commented 2 years ago

@tolotrasamuel explained very accurately. Did anyone found any solution till now?

gslender commented 2 years ago

I'd like to see this feature too

JasonWangex commented 2 years ago

I have the same experience as you. In the end, I chose Overlay to achieve this. The side effect is that this dialog is always at the top level.

aytunch commented 2 years ago

The same problem exists in the case of showModalBottomSheet. In our app, many sheets can be presented on top of each other and we need to have the ability to control which ones get popped. Navigator.pop() is not a sufficient API for Dialogs and Sheets.

GiddyNaya commented 2 years ago

Many waiting months later... :/

justprodev commented 2 years ago

You can play around this

final route = DialogRoute(
    context: context,
    builder: (_)=>MyDialogView(),
    barrierDismissible: false
);

Navigator.of(context).push(route);

// close dialog
Navigator.of(context).removeRoute(route);

Or dive into https://github.com/flutter/flutter/blob/4f034a40454b86064fdcb9dce156975c3fad0572/packages/flutter/lib/src/material/dialog.dart#L1064

Petri-Oosthuizen commented 1 year ago

You can play around this

final route = DialogRoute(
    context: context,
    builder: (_)=>MyDialogView(),
    barrierDismissible: false
);

Navigator.of(context).push(route);

// close dialog
Navigator.of(context).removeRoute(route);

Or dive into

https://github.com/flutter/flutter/blob/4f034a40454b86064fdcb9dce156975c3fad0572/packages/flutter/lib/src/material/dialog.dart#L1064

Important to note the following from Navigator.removeRoute:

The removed route is disposed without being notified. The future that had been returned from pushing that routes will not complete.

You'll probably have to play around with completers and the like to get this to work as expected.

sgehrman commented 1 year ago

I have a case where a info dialog comes up after a delay. if the user has a dialog open and calls pop, the pop will be used on the info dialog and not the dialog I want to close. Any work arounds that work correctly in all cases?

MelbourneDeveloper commented 1 year ago

Using pop to close a dialog doesn't make much sense. You need to find out if the dialog is currently on screen. It could have already been dismissed, and popping would be like hitting the back button.

Oooooori commented 1 year ago

any updated?

sergiotucano commented 1 year ago

any updated?

prologikus commented 1 year ago

Extended showDialog:

Future<T?> showDialogAdvance<T>({
  required WidgetBuilder builder,
  bool barrierDismissible = true,
  bool closePreviousDialog = false,
}) {
  final appServ = AppService.instance;
  final context = appServ.overlayCtx();

  assert(debugCheckHasMaterialLocalizations(context));

  final CapturedThemes themes = InheritedTheme.capture(
    from: context,
    to: Navigator.of(
      context,
      rootNavigator: true,
    ).context,
  );

  if (closePreviousDialog) {
    appServ.closeCurrentDialog();
  }

  final route = DialogRoute<T>(
    context: context,
    builder: builder,
    barrierColor: Colors.black54,
    barrierDismissible: barrierDismissible,
    barrierLabel: null,
    useSafeArea: true,
    settings: null,
    themes: themes,
    anchorPoint: null,
    traversalEdgeBehavior: TraversalEdgeBehavior.closedLoop,
  );

  final future = Navigator.of(context, rootNavigator: true).push<T>(route);

  appServ.setCurrentDialogRoute(route);

  future.whenComplete(() => appServ.setCurrentDialogRoute(null));

  return future;
}

Navigation Service:

class AppService {
  static final instance = AppService._();

  AppService._();

  RootStackRouter? _appRouter;
  RootStackRouter? get appRouter => _appRouter;
  void setRouter(RootStackRouter appRouter) {
    _appRouter ??= appRouter;
  }

  BuildContext overlayCtx() => _appRouter!.navigatorKey.currentContext!;

  /// Dialog
  DialogRoute? _dialogRoute;
  void closeCurrentDialog() {
    if (_dialogRoute != null) {
      Navigator.of(overlayCtx(), rootNavigator: true)
          .removeRoute(_dialogRoute!);
      _dialogRoute = null;
    }
  }

  void setCurrentDialogRoute(DialogRoute? dialogRoute) =>
      _dialogRoute = dialogRoute;
}
a1573595 commented 1 year ago

In my case, I needed to open the bottom sheet while closing the dialog, but the sheet was closed instead.

Amdian commented 9 months ago

I have the same experience as you. In the end, I chose Overlay to achieve this. The side effect is that this dialog is always at the top level.

too,i'm hate the getx and flutter

zoomkoding commented 7 months ago

Please check this issue.

flutter-triage-bot[bot] commented 6 months ago

This issue is missing a priority label. Please set a priority label when adding the triaged-design label.

Trung15010802 commented 4 months ago

2024 👍

Epilogue-P commented 4 months ago

2950D138 这个问题竟然经历了 4 年。

Rosario9527 commented 3 weeks ago

Any solution now?