How to close specific Dialog - flutter

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.

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

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);

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"),
),
),
);
}
}

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!

Related

Plutogrid show dialog after iconbutton pressed

how can I show an dialog widget after a click on an Iconbutton in a plutogrid cell?
This is my code in der renderer for the plutogrid cell:
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
IconButton(
icon: Icon(
Auswahl,
),
onPressed: () {}, => here I want to show a dialog widged...
iconSize: 18,
color: Colors.black,
),
]);
And here is the code for the dialog:
void _selectDialog() async {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Add Info'),
content: setupSelectDialoagContainer(),
);
});
}
I tried to call _selectDialog(), but I got the error:
The instance member '_selectDialog' can't be accessed in an initializer.
I don't know why you faced this error, this is my code showing the same dialog as yours and it's working.
Here's the code:
class Demo extends StatefulWidget {
const Demo({super.key});
#override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
void _selectDialog() async {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Add Info'),
// content: setupSelectDialoagContainer(),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Center(
child: IconButton(
onPressed: () {
_selectDialog();
},
icon: Icon(Icons.abc),
),
)
],
),
);
}
}

Flutter - Navigate in PopupMenuButton onTap does not work

I used to use IconButton's onpressed to navigate to the settings page from my AppBar which worked.
Now I am trying to trigger the navigation from a PopupMenuItem's onTap but the page does not navigate. Both widgets are in the same hierarchy and I can't find out the reason for the different behavior. No error is thrown.
Here is the code of my appBar which contains the actions:
Widget build(BuildContext ctx) {
return Container(
child: Scaffold(
appBar: AppBar(
title: const Text('MyApp'),
actions: [
PopupMenuButton(
itemBuilder: (context) => [
// THE NAVIGATION IN onTap DOES NOT WORK
PopupMenuItem(
child: Text(AppLocalizations.of(context).settings),
onTap: () => _openSettings(ctx),
),
],
icon: Icon(
Icons.settings,
),
),
// THIS WORKS
IconButton(onPressed: () => _openSettings(ctx),
icon: Icon(Icons.settings))
],
),
body: Text("")
),
);
}
And here the function whose navigation call only works inside IconButton's onpressed.
I could confirm that the function was triggered in both cases though:
Future<void> _openSettings(BuildContext ctx) async {
print('settings');
await Navigator.push(
ctx, MaterialPageRoute(builder: (ctx) => SettingsPage()));
print('settings completed');
}
I'd appreciate any help!
Workaround:
I have not found out what the issue with onTap navigation is but now I am just using onSelected which results in the same UX and works:
Widget build(BuildContext ctx) {
return Container(
child: Scaffold(
appBar: AppBar(
title: const Text('MyApp'),
actions: [
PopupMenuButton(
onSelected: (result){
switch(result){
case 0: _openSettings(); break;
case 1: _signOut(); break;
}
},
itemBuilder: (context) => [
PopupMenuItem(
child: Text(AppLocalizations.of(context).settings),
value: 0
),
PopupMenuItem(
child: Text(AppLocalizations.of(context).logout),
value: 1
)
],
icon: Icon(
Icons.settings,
),
)
],
)
)
);
}
I had the same issue (except: I am using GetX for Navigation.
I think this happens because flutter is closing the PopupMenuButton automatically and because navigation happens to fast, it closes the new route instead of the menuButton.
I solved it like this in the navigating method:
void edit() async {
await Future.delayed(const Duration(milliseconds: 10));
Get.toNamed('/new/route'); // same like Navigator.of(context).pushNamed()
}
It's not beautiful... i know. But it works XD
I also faced this problem and solved it as follows:
First you must remove onTap or make its value null.
Second, give a value to the value parameter in PopupMenuItem. You can give any type like int or String or whatever you want since value is dynamic.
then, pass a function to the onSelected parameter in the PopupMenuButton.
Inside the onSelected function you can choose what you want to do when any of the menu items is pressed.
In your case, your code should be like this 👇
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
child: Text(...),
value: 'settings',
),
],
onSelected: (value){
if(value == 'settings'){
_openSettings(ctx);
}
},
),
you need to invoke the future function using ()=>, and pass Build Context for the function to know which context to navigate from.
so your button will change like this:
IconButton(onPressed:()=> _openSettings(context), icon: Icon(Icons.settings))
and your function should be like this:
Future<void> _openSettings(BuildContext ctx) async {
print('settings');
await Navigator.push(
ctx, MaterialPageRoute(builder: (context) => SettingPage()));
print('settings completed');
}
here is the entire code that I've tested the issue on:
class PlayGround extends StatefulWidget {
const PlayGround({Key? key}) : super(key: key);
#override
_PlayGroundState createState() => _PlayGroundState();
}
class _PlayGroundState extends State<PlayGround> {
final _title =
TextEditingController.fromValue(TextEditingValue(text: 'initialTitle'));
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MyApp'),
actions: [
PopupMenuButton<int>(
itemBuilder: (context) => [
PopupMenuItem(
child: Text(AppLocalizations.of(context).settings),
onTap: () {},
),
PopupMenuItem(
child: Text(AppLocalizations.of(context).logout),
onTap: () {},
)
],
icon: Icon(
Icons.settings,
),
),
IconButton(
onPressed: () => _openSettings(context),
icon: Icon(Icons.settings))
],
),
body: Container());
}
Future<void> _openSettings(BuildContext ctx) async {
print('settings');
await Navigator.push(
ctx, MaterialPageRoute(builder: (context) => SettingPage()));
print('settings completed');
}
}
class SettingPage extends StatelessWidget {
const SettingPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
}

Flutter awaiting result of dialog

i have the following code which is called by a click of a FlatButton:
_performOrderCheck(BuildContext context) async {
bool _checksCompleted = await _performBundleCheck(context);
print("Sepp");
print(_checksCompleted);
if (_checksCompleted) {
_addArticleToOrder(_innerQty, _article);
Navigator.pop(context);
}
}
Future<bool> _performBundleCheck(BuildContext context) async {
//check bundles
if (!_article.checkBundeledArticles()) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Menü unvollständig'),
content: Text(
'Sie haben nicht alle möglichen Artikel gewählt. Wollen sie dennoch fortfahren?'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(_);
return false;
},
child: Text('Nein')),
FlatButton(
onPressed: () {
//_addArticleToOrder(_innerQty, _article);
Navigator.pop(_);
return true;
//Navigator.pop(context);
},
child: Text('Ja')),
],
elevation: 24,
),
barrierDismissible: false);
} else {
return true;
}
}
What i would like is that the could waits for the user decision and then it calls "_addArticleToOrder". Is that possible?
Thanks for any help.
You can add await keyword in-front of showdialog and return value at the end of show dialog.
added await.
await showDialog(
add return value
barrierDismissible: false);
return true; // added line
While the accepted answer is working the result is always returning true.
If you want to get the result of the dialog, which could be false by clicking 'Nein' and true by clicking 'Ja', here´s the code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
_performOrderCheck(context);
},
// onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
_performOrderCheck(BuildContext context) async {
_incrementCounter();
print("_performOrderCheck called");
bool _checksCompleted = await _performBundleCheck(context);
print("_checksCompleted result: $_checksCompleted");
if (_checksCompleted) {
print("_checksCompleted");
}
}
Future<bool> _performBundleCheck(BuildContext context) async {
//check bundles
if (true) {
return await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Menü unvollständig'),
content: Text(
'Sie haben nicht alle möglichen Artikel gewählt. Wollen sie dennoch fortfahren?'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(context, false);
},
child: Text('Nein')),
FlatButton(
onPressed: () {
Navigator.pop(context, true);
},
child: Text('Ja')),
],
elevation: 24,
),
barrierDismissible: false,
);
} else {
return true;
}
}
}
Using Navigator.pop(context, false); and Navigator.pop(context, true); returns the result of the dialog to showDialog.
Using return await returns it then from the _performBundleCheck function to _performOrderCheck.

Navigator.of(context).pop() Give me black screen

Noob here.
I've made a update checker with flutter, but if I choose any button, it give me black screen.
How can I fix this? Any ideas?
Code
Full Source : https://github.com/aroxu/LiteCalculator
Dialog Part Source :
import 'package:LiteCalculator/updater/bean/UpdaterBean.dart';
import 'package:flutter/material.dart';
class UpdateHolder extends StatelessWidget {
final List<Version> version;
UpdateHolder({Key key, this.version}) : super(key: key);
#override
Widget build(BuildContext context) {
return calculateResult(
version[0].latestVersion, version[1].currentVersion, context);
}
Widget calculateResult(latestVersion, currentVersion, context) {
print('Latest Version : ${int.parse(latestVersion)}');
print('Current Version : ${int.parse(currentVersion)}');
Widget data;
if ((int.parse(currentVersion) <= int.parse(latestVersion))) {
data = Center(
child: createAlert('Update Required', actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
print('OK Button Pressed.');
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Later'),
onPressed: () {
print('Later Button Pressed.');
Navigator.of(context).pop();
},
),
]),
);
} else
data = Center();
return data;
}
Widget createAlert(content, {List<Widget> actions, title}) {
AlertDialog snackBar;
snackBar = AlertDialog(
content: Text(content),
actions: actions,
);
return snackBar;
}
}
call this for your popup,
void showDialogPopup(){
showDialog(
context: context,
builder: (_)=>AlertDialog(
backgroundColor: Colors.transparent,
content: Container(
child: Center(
child: FlatButton(
onPressed: (){
Navigator.of(context).pop(null);
},
child: Center(
child: Text("close")
)
)
)
)
)
);
}
A black screen or a blank screen? If its a black screen, your are not wrapping your main widget (which goes in the runApp) with a MaterialApp.
You can refer this.
I used url_launcher 5.4.1 to open PlayStore web.
import "package:flutter/material.dart";
import 'package:url_launcher/url_launcher.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: FirstPage(),
debugShowCheckedModeBanner: false,
);
}
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
void initState() {
_checkUpdate();
super.initState();
}
Future<void> _checkUpdate() async {
await Future.delayed(Duration.zero);
await showDialog(
context: context,
builder: (context) => UpdateDialog(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("First Page"),
),
);
}
}
class UpdateDialog extends StatefulWidget {
#override
_UpdateDialogState createState() => _UpdateDialogState();
}
class _UpdateDialogState extends State<UpdateDialog> {
Future<void> _updateFound;
#override
void initState() {
_updateFound = _checkForUpdate();
super.initState();
}
Future<bool> _checkForUpdate() async {
await Future.delayed(Duration.zero);
bool updateFound = false;
await Future.delayed(Duration(seconds: 3)); // Do Get call to server
updateFound = true;
if (!updateFound) Navigator.pop(context);
return updateFound;
}
Future<void> _openWebPage() async {
Navigator.pop(context);
launch("https://play.google.com"); //Your link `url_launcher` package
}
void _laterClicked(){
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _updateFound,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
const SizedBox(height: 12.0),
Text("Checking for Update"),
],
),
);
else if (snapshot.hasError)
return AlertDialog(
title: Text("Error Occured"),
content: Text("ERROR: ${snapshot.error}"),
);
else if(snapshot.data)
return AlertDialog(
title: Text("Update Required"),
content: Text(
"Latest version found. Need an update. bla bla bla bla bla bla"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: _openWebPage,
),
FlatButton(
child: Text("LATER"),
onPressed: _laterClicked,
),
],
);
else
return const SizedBox();
},
);
}
}
This happens whenever you try to pop using the Widget's context.
In the following code:
FlatButton(
child: Text('OK'),
onPressed: () {
print('OK Button Pressed.');
Navigator.of(context).pop();
},
)
context represents the context of the widget, itself (provided in the build method).
To resolve this issue instead of creating a Dialog widget and returning it as the main widget, just use showDialog and return a simple Container().
Use dialogContext to pop the dialog and not the widget itself.
for example:
if ((int.parse(currentVersion) <= int.parse(latestVersion))) {
showDialog(
builder: (dialogContext) => AlertDialog(
content: Text('Update Required'),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
print('OK Button Pressed.');
Navigator.of(dialogContext).pop();
},
),
FlatButton(
child: Text('Later'),
onPressed: () {
print('Later Button Pressed.');
Navigator.of(dialogContext).pop();
},
),
],
),
);
}
return Container();
I had a similar problem and my solution was something like that:
bool hasBeenShown = false;
if(!hasBeenShown) {
Navigator.pop(context);
}
hasBeenShown = true;
The problem for me was that for some reason Navigator.pop been invoked multiple times when it's supposed to be invoked only once.

flutter: Another exception was thrown: No MaterialLocalizations found

I am trying to show an Alert Dialog on press of a button in Flutter.
Following is my code
main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Different Widgets",
debugShowCheckedModeBanner: false,
home: showAlertDialog()
);
}
void _dialogResult(String value) {
if (value == "YES") {
print("YES");
} else {
print("NO");
}
Navigator.pop(context);
}
Widget showAlertDialog() {
TextEditingController textEditingController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text("Different Widgets"),
),
body: Container(
child: Center(
child: Column(
children: <Widget>[
TextField(
controller: textEditingController,
),
RaisedButton(
onPressed: () {
print("Hi");
AlertDialog dialog = AlertDialog(
title: Text("Hi"),
content: Text(
textEditingController.text,
style: TextStyle(fontSize: 30.0),
),
actions: <Widget>[
FlatButton(
onPressed: () {
_dialogResult("YES");
},
child: Text("YES")),
FlatButton(
onPressed: () {
_dialogResult("NO");
},
child: Text("NO")),
],
);
showDialog(context: context, builder: (BuildContext context) => dialog);
},
child: Text("Click Me"),
)
],
),
),
),
);
}
What does this has to do with Localisation, I cannot follow. I did the same steps as per the docs. I am able to see the button but on click of that button I keep getting error. I tried writing print statement inside of button click and the print statement appears in the log, definitely something wrong with AlertDialog.
You may get No MaterialLocalizations found error while showing dialog using showDialog() class in Flutter. The issue is putting child widget on home property of MaterialApp() widget without creating new widget class.
One way to solve is putting MaterialApp() inside runApp() and create new class for home property.
import 'package:flutter/material.dart';
main() {
runApp(
MaterialApp(
home: MyApp(),
title: "Different Widgets",
debugShowCheckedModeBanner: false,
),
);
}
/*
place MaterialApp() widget on runApp() and create
new class for its 'home' property
to escape 'No MaterialLocalizations found' error
*/
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return showAlertDialog();
}
void _dialogResult(String value) {
if (value == "YES") {
print("YES");
} else {
print("NO");
}
Navigator.pop(context);
}
Widget showAlertDialog() {
TextEditingController textEditingController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text("Different Widgets"),
),
body: Container(
child: Center(
child: Column(
children: <Widget>[
TextField(
controller: textEditingController,
),
RaisedButton(
onPressed: () {
print("Hi");
AlertDialog dialog = AlertDialog(
title: Text("Hi"),
content: Text(
textEditingController.text,
style: TextStyle(fontSize: 30.0),
),
actions: <Widget>[
FlatButton(
onPressed: () {
_dialogResult("YES");
},
child: Text("YES")),
FlatButton(
onPressed: () {
_dialogResult("NO");
},
child: Text("NO")),
],
);
showDialog(
context: context,
builder: (BuildContext context) => dialog);
},
child: Text("Click Me"),
)
],
),
),
),
);
}
}