4

I am opening a dialog from another dialog and trying to close the 1st dialog, but it is closing the recent dialog. Similar kind of git issue.

I've tried

  • putting ValueKey on AlertDialog
  • using rootNavigator:true while pop
  • keeping context into variable and doing Navigator.of(specifiqContext).pop()

But none of them is working. Code to reproduce the issue on dartPad.

class MultiDialogTest extends StatefulWidget {
  const MultiDialogTest({Key? key}) : super(key: key);

  @override
  State<MultiDialogTest> createState() => _MultiDialogTestState();
}

class _MultiDialogTestState extends State<MultiDialogTest> {
  BuildContext? dialog1Context, dialog2Context;

  Future<void> _showDialog1(BuildContext context) async {
    await showDialog(
        context: context,
        barrierDismissible: false,
        builder: (c) {
          dialog1Context = c;
          return AlertDialog(
            key: const ValueKey("dialog 1"),
            title: const Text("Dialog 1"),
            content: ElevatedButton(
              child: const Text("close dialog2"),
              onPressed: () {
                if (dialog2Context != null) {
                  Navigator.of(dialog2Context!,).pop();
                }
              },
            ),
            actions: [
              ElevatedButton(
                child: const Text("close this"),
                onPressed: () {
                  Navigator.of(c, rootNavigator: true).pop();
                },
              ),
            ],
          );
        });

    dialog1Context = null;
  }

  Future<void> _showDialog2(BuildContext context) async {
    await showDialog(
        context: context,
        barrierDismissible: false,
        builder: (c) {
          dialog2Context = c;
          return AlertDialog(
            key: const ValueKey("dialog 2"),
            title: const Text("Dialog 2"),
            actions: [
              ElevatedButton(
                child: const Text("close this"),
                onPressed: () {
                  Navigator.of(c, rootNavigator: true).pop();
                },
              ),
            ],
            content: Column(
              children: [
                ElevatedButton(
                  onPressed: () async {
                    await _showDialog1(context);
                  },
                  child: const Text("Open dialog 1"),
                ),
              ],
            ),
          );
        });
    dialog2Context = null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _showDialog2(context);
          },
          child: const Text("show dialog 2"),
        ),
      ),
    );
  }
}

How can I close bellow Dialog(Dialog 2) without closing above(Dialog 1).

I don't like to close both and reopen the Dialog 1.

enter image description here

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • i am afraid you cannot do that – pskink Feb 17 '22 at 10:32
  • 1
    @pskink Can you provide something that explain the situation? I am trying to understand flutter. – Md. Yeasin Sheikh Feb 17 '22 at 12:42
  • 1
    basically `showDialog` uses `NavigatorState.push` method under the [hood](https://github.com/flutter/flutter/blob/db747aa133/packages/flutter/lib/src/material/dialog.dart#L1042) and as far i know you cannot remove the route from the middle of the navigator stack – pskink Feb 17 '22 at 12:59
  • the only way you could do it is to use `NavigatorState.removeRoute` method but you would need to know what `Route` to remove – pskink Feb 17 '22 at 16:01
  • Seems complicated, While looking for this I found : NavigatorState doesn't expose an API for getting the path of the current route, – Md. Yeasin Sheikh Feb 17 '22 at 16:08
  • 1
    thats why you would need to create some class like `DialogHelper` that has method like `showDialog` that keeps track of shown dialogs and `deismissDialog` that uses `NavigatorState.removeRoute` under the hood – pskink Feb 17 '22 at 17:03

5 Answers5

2

You need to pass context of the dialog you want to close (parentContext) and call:

Navigator.pop(parentContext); // close parent 
Navigator.pop(context); // close current 
Qays Dwekat
  • 127
  • 1
  • 4
2

I see that there's still no answer so here's what I did:

void showMyDialog(BuildContext context, Widget dialogContent) async {
  var myDialogRoute = DialogRoute(
    context: context,
    builder: (BuildContext context) {
      return Dialog(child: dialogContent);
    },
  );

  /// push the dialog route
  Navigator.of(context).push(myDialogRoute);

  /// do your work here
  await Future.delayed(2.seconds);

  /// ensure the route is still active to avoid removing a route that doesn't
  /// exist causing Navigator error
  if (myDialogRoute.isActive) {
    /// whenever you want, remove the specific route even if other dialogs
    /// are spawned above or whatever
    Navigator.of(context).removeRoute(myDialogRoute);
  }
}

you can also return the myDialogRoute and use it remotely. Just make sure you dismiss the dialog before disposing the context.

BananaMaster
  • 377
  • 6
  • 15
1

Create a separate context and pass the correct context which one you want to close to the Navigator.pop(yourContextThatYouWishToClose)

Navigator.pop(dialogContext);

Here is the example code.

BuildContext dialogContext; // <<----
  showDialog(
    context: context, // <<----
    barrierDismissible: false,
    builder: (BuildContext context) {
      dialogContext = context;
      return Dialog(
        child: new Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            new CircularProgressIndicator(),
            new Text("Loading"),
          ],
        ),
      );
    },
  );

  await _longOperation();
  Navigator.pop(dialogContext);
Anand
  • 4,355
  • 2
  • 35
  • 45
-1

What you could do is pop twice in showDialog1 and then await for showDialog1 immediately.

import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

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

 
class MultiDialogTest extends StatefulWidget {
  const MultiDialogTest({Key? key}) : super(key: key);

  @override
  State<MultiDialogTest> createState() => _MultiDialogTestState();
}

class _MultiDialogTestState extends State<MultiDialogTest> {

  Future<void> _showDialog1(BuildContext context) async {
    await showDialog(
        context: context,
        barrierDismissible: false,
        builder: (c) {
          return AlertDialog(
            key: const ValueKey("dialog 1"),
            title: const Text("Dialog 1"),
            content: ElevatedButton(
              child: const Text("close dialog2"),
              onPressed: () async {
                  Navigator.of(context).pop();
                  Navigator.of(context).pop();
                  await _showDialog1(context);
              },
            ),
            actions: [
              ElevatedButton(
                child: const Text("close this"),
                onPressed: () {
                  Navigator.of(c).pop();
                },
              ),
            ],
          );
        });
  }

  Future<void> _showDialog2(BuildContext context) async {
    await showDialog(
        context: context,
        barrierDismissible: false,
        builder: (c) {
          return AlertDialog(
            key: const ValueKey("dialog 2"),
            title: const Text("Dialog 2"),
            actions: [
              ElevatedButton(
                child: const Text("close this"),
                onPressed: () {
                  Navigator.of(c).pop();
                },
              ),
            ],
            content: Column(
              children: [
                ElevatedButton(
                  onPressed: () async {
                    await _showDialog1(context);
                  },
                  child: const Text("Open dialog 1"),
                ),
              ],
            ),
          );
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _showDialog2(context);
          },
          child: const Text("show dialog 2"),
        ),
      ),
    );
  }
}
Kantine
  • 747
  • 6
  • 14
-1

When you use showDialog what happens under the hood is a simple push on the Navigator, this will add the Dialog on the top of the current Navigator as a new Route.

All the pop methods in Navigator simply pop from the topmost route so this is not easily feasible.

A dirty hack may be to pop twice and show again the first dialog like in this sample that works in your dartpad sample

onPressed: () {
  if (dialog2Context != null) {
       Navigator.of(dialog2Context!).pop();
       Navigator.of(dialog2Context!).pop();
       _showDialog1(context);
  }
},

In my opinion, having a dialog spawning another dialog its not the best UX you can provide to your user, but you can always check which routes are involved by using the inspector:

https://docs.flutter.dev/development/tools/devtools/inspector

in this case, you can quickly check that the dialog will be always on top (in this case the latest of the tree), the proper way to fix this should be to create several navigators and decide which one to use for showing your dialog, but that will complexity a lot of your code!

enter image description here

CLucera
  • 1,091
  • 11
  • 29