Issue with AlertDialog being triggered by a button - flutter

I am trying to have an alert dialogue appear after I click a button but clicking the button doesn't do anything for some reason. I have it in the onPressed so I'm not sure why it isn't triggering. Also if I get this working will it work with Apple devices as well since this is a materials widget.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.bottomCenter, // align the row
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
onPressed: (){
AlertDialog(
title: Text("Test"),
content: Text("This is a test..."),
elevation: 24.0,
);
},
color: Colors.blue,
splashColor: Colors.white,
elevation: 2,
)
)
],
)
)
);
}
}

You have to use the showDialog method to make a dialog appear. Inside the builder callback, you then define the type of dialog you want ex. AlertDialog.
Example (from https://medium.com/#nils.backe/flutter-alert-dialogs-9b0bb9b01d28):
//your method
void _showDialog(BuildContext context) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Alert Dialog title"),
content: new Text("Alert Dialog body"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Your new code:
onPressed: () => _showDialog(context); // only pass context if you're in a stateless widget
Regarding Apple devices, instead of AlertDialog, you can use CupertinoAlertDialog if you're exclusively developing for iOS.

Related

How to make only two buttons active in Flutter/Dart?

In my code, there is a condition that pops up the following dialog:
Its code looks like this:
//Alert Dialog about questions and answers
void _showAlertDialog() {
// set up the buttons
Widget Answer1Button = TextButton(
child: Text(_listData[_listCount][3]),
onPressed: () {},
);
Widget Answer2Button = TextButton(
child: Text(_listData[_listCount][4]),
onPressed: () {},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text(),
content: Text(_listData[_listCount][2]),
actions: [
Answer1Button,
Answer2Button,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
I need my user to not be able to tap on the screen or press the back button. Only two buttons need to be active (Answer 1.1 and Answer 1.2 in this example).
How to do it? Thanks in advance.
Edit1. I don't know what I'm doing wrong. Here is my code where I followed your advice:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
await showDialog;
// await showDialog or Show add banners or whatever
// return true if the route to be popped
return false; // return false if you want to disable device back button click
},
child: Scaffold(
appBar: AppBar(
title: const Text('New Game'),
),
body: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(assetPath),
fit: BoxFit.cover)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Visibility(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('files/sheet.jpg'),
fit: BoxFit.cover)),
),
),
Text(_listData[_listCount][0]),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ImageButton(label: 'OK', onButtonTap: _nextCSV),
ImageButton(label: 'Hide', onButtonTap: _isVisible),
ImageButton(label: 'Test1', onButtonTap: _showAlertDialog),
],
),
],
),
visible: isVisible,
),
// your other widgets
Visibility(
child: ImageButton(label: 'Show', onButtonTap: _isVisible),
visible: !isVisible,
)
],
),
),
),
);
}
But nothing has changed. I believe that I made a mistake here:
await showDialog;
However, when I do like this, nothing changes either. The back button still works:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false; // return false if you want to disable device back button click
},
child: Scaffold(
...
As a result, I can still click on the back button. Although I want it to be inactive on the dialog. How to fix it?
You should wrap the AlertDialog with OnWillPop, not the Scaffold, since you need to set the action pop for the AlertDialog widget.
Here's a minimal example of how to implement the OnWillPop widget to match your case:
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_showAlertDialog();
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
),
child: const Text(
"Open Dialog",
style: TextStyle(color: Colors.white),
))),
);
}
void _showAlertDialog() {
// set up the buttons
Widget Answer1Button = TextButton(
child: Text("Button 1"),
onPressed: () {
Navigator.pop(context);
},
);
Widget Answer2Button = TextButton(
child: Text("Button 2"),
onPressed: () {
Navigator.pop(context);
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text(),
content: Text("Alert Dialog"),
actions: [
Answer1Button,
Answer2Button,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: alert);
},
);
}
}
You can pass a parameter named 'barrierDismissible' in the showDialog, set it to false.
showDialog(
barrierDismissible: false,
builder: ...
)
There is one parameter called barrierDismissible which you can use to dismiss any tap of background of dialog,
showDialog(
barrierDismissible: false,
)
You can disable back button by wrapping the scaffold with WillPopScope.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// await showDialog or Show add banners or whatever
// return true if the route to be popped
return false; // return false if you want to disable device back button click
},
child: Scaffold(),
),
};
Here is the docs links
https://api.flutter.dev/flutter/widgets/WillPopScope-class.html

How to display SnackBar in Flutter?

I want to display a SnackBar in my Flutter app. I have read the docs and copyed it:
The body of my scaffold:
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Osztályok"),
leading: Padding(
padding: const EdgeInsets.only(left: 5.0),
child: IconButton(
icon: Icon(Icons.exit_to_app, color: Colors.white70),
onPressed: () {
authService.signOut();
authService.loggedIn = false;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GoogleSignUp()));
})),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 5.0),
child: Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.add_circle_outline,
color: Colors.white70),
onPressed: () {
createPopup(context);
}),
// IconButton(
// icon: Icon(Icons.search, color: Colors.black38),
// onPressed: null),
],
)),
],
),
The SnackBarPage class:
class SnackBarPage extends StatelessWidget {
void jelszopress(TextEditingController jelszoController, BuildContext context) async{
var jelszo;
DocumentReference docRef =
Firestore.instance.collection('classrooms').document(globals.getid());
await docRef.get().then((value) => jelszo= (value.data['Jelszo']) );
if (jelszo == jelszoController.text.toString()){
Navigator.push(context,
MaterialPageRoute(builder: (context) => InClassRoom()));
}
else{
Navigator.pop(context);
final snackBar = SnackBar(content: Text('Yay! A SnackBar!'));
Scaffold.of(context).showSnackBar(snackBar);
}
}
Future<String> jelszoba(BuildContext context) {
TextEditingController jelszoController = TextEditingController();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Add meg a jelszót'),
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: TextField(
controller: jelszoController,
decoration: InputDecoration(hintText: "Jelszó")
)
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text('Mehet'),
onPressed: () {
jelszopress(jelszoController, context);
},
)]);
}
);
}
var nevek;
var IDS;
SnackBarPage(this.nevek, this.IDS);
#override
Widget build(BuildContext context){
return ListView.builder(
itemCount: nevek.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
globals.setid(IDS[index]);
jelszoba(context);
},
title: Text(nevek[index]),
),
);
},
) ;
}
}
But my cody doesn't display the SnackBar. I tried the solution of this question: How to properly display a Snackbar in Flutter? but adding a Builder widget didn't help.
"Scaffold.of(context)" has been deprecated, will return null. Now use "ScaffoldMessenger.of(context)". As per Flutter documentation.
#override
Widget build(BuildContext context) {
// here, Scaffold.of(context) returns null
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('snack'),
duration: const Duration(seconds: 1),
action: SnackBarAction(
label: 'ACTION',
onPressed: () { },
),
));
},
child: const Text('SHOW SNACK'),
),
),
);
}
NOTE: Make sure your main.dart overrided build() function should return "MaterialApp" as a widget, such as:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Must be MaterialApp widget for ScaffoldMessenger support.
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyDashboard(),
);
}
}
So based on the error, it would seem that the context passed in Snackbar.of() is not the correct context. This would make sense based on 1 & 2; and summary copied below:
Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function. (And similarly, the parent of any children for RenderObjectWidgets.)
In particular, this means that within a build method, the build context of the widget of the build method is not the same as the build context of the widgets returned by that build method.
So this means that the build context you are passing in jelszoba(context) function is not the build context you need and is actually the build context of the widget that is instantiating the Scaffold.
So How to Fix:
To fix this wrap your Card widget in your SnackbarPage in a Builder widget and pass the context from it, to the jelszoba(context) method.
An example from 1 I post below:
#override
Widget build(BuildContext context) {
// here, Scaffold.of(context) returns null
return Scaffold(
appBar: AppBar(title: Text('Demo')),
body: Builder(
builder: (BuildContext context) {
return FlatButton(
child: Text('BUTTON'),
onPressed: () {
// here, Scaffold.of(context) returns the locally created Scaffold
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello.')
));
}
);
}
)
);
}
You can normally use snack bar in the Bottom Navigation bar in this way. However, if you want to show it in the body, then just copy the code from Builder and paste it in the body of the scaffold.
Scaffold(bottomNavigationBar: Builder(builder: (context) => Container(child: Row(children: <Widget>[
Icon(Icons.add_alarm), Icon(Icons.map), IconButton(icon: Icon(Icons.bookmark),
onPressed:() {
Scaffold.of(context).showSnackBar(mySnackBar);
final mySnackBar = SnackBar(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.white, duration: Duration(seconds: 1),
content: Text(
'Article has been removed from bookmarks',
),);
}
),
],
),
),
),
);
Note: In the behaviour property of SnackBar, you can just leave it empty. But the problem with that is "If you have Curved Navigation Bar or you have a floating action button above the bottom navigation bar, then the snackbar will lift these icons (or FAB ) and will affect the UI". That's why SnackBar.floating is more preferred as it is more capatible with the UI.
But you can check and see on your own which suits you the best.

How to pass a variable reference between different routes?

Im writing a simple gamebook game in flutter with menu, game and options route. In option route there is button that on pressed should delete all saved games.
Right at this moment Im loading saved games on application launch from SharedPreferences. Right after loading them I set up boolean _savedGame that im using in 'Continue' button in menu route and 'Delete saved games' button in options route to activate or deactivate them. The whole problem is - i dont know how to change variables in menu route from option route. When im creating option route I give it _savedGame so that it knows if it should render active or deactivated button.
PS. Yes, I know that right now im sending option route a copy of _savedGame variable.
Menu route option page button.
RaisedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OptionsPage(_savedGame),
),
),
Option page
class OptionsPageState extends State<OptionsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Options",
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.blueGrey[900],
),
body: Container(
alignment: Alignment.center,
color: Colors.cyan,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child:
Text('Delete saved games', style: TextStyle(fontSize: 40)),
onPressed:
widget.isGameSaved ? () => _showWarning(context) : null,
),
const SizedBox(height: 30),
RaisedButton(
child: Text('Back', style: TextStyle(fontSize: 40)),
onPressed: () {
Navigator.pop(context);
},
),
])),
),
);
}
Future<void> _showWarning(BuildContext context) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Are you sure you want to delete saved game?'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
saveGame('empty');
Navigator.of(context).pop();
setState(() {
widget.isGameSaved = false;
});
},
),
],
);
},
);
}
}
How do I "setState" for variables in different routes?
What you could do is have an InheritedWidget (call it say GameStateWidget) above your Navigator (or MaterialApp if you're using its navigator). In the InheritedWidget have a ValueNotifier, say savedGame that has the value you want to share.
Then in the route where you need to set the value
GameStateWidget.of(context).savedGame.value = ...
And in the route where you need the value
ValueListenableBuilder(
valueListenable: GameStateWidget.of(context).savedGame,
builder: (context, savedGameValue, child) => ...
)

Show Flutter's SnackBar above a visible Drawer menu?

I have a Scaffold with a simple Drawer in which I show a menu where a user can press a button. When this button is pressed I want to display a SnackBar, but the SnackBar is always displayed behind the Drawer. Is there some way to show it in front of the Drawer?
The drawer's code looks like:
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.lock_open),
title: Text('Click Me'),
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
'Test.',
)));
},
),
],
),
);
}
}
and it is used directly in the Scaffold:
return Scaffold(
drawer: MyDrawer(),
[...]
They said Drawer must be on the top of UI under MaterialDesign rules. But if you wanna such behaviour too much you can write own SnackBar.
static void showSnackBarAsBottomSheet(BuildContext context, String message)
{
showModalBottomSheet<void>(
context: context,
barrierColor: const Color.fromRGBO(0, 0, 0, 0),
builder: (BuildContext context) {
Future.delayed(const Duration(seconds: 5), () {
try {
Navigator.pop(context);
} on Exception {}
});
return Container(
color: Colors.grey.shade800,
padding: const EdgeInsets.all(12),
child: Wrap(children: [
Text(
message,
style:
GoogleFonts.robotoCondensed().copyWith(color: Colors.white),
)
]));
},
);
}
I've solved it by adding one more Scaffold inside Drawer and making its background transparent:
return Scaffold(
drawer: Scaffold(
backgroundColor: Colors.transparent,
body: MyDrawer(),
[...]

How to prevent AlertDialog from not closing by clicking outside?

I built an AlertDialog to display Loading while i'm authenticating the user and when it finishes i pop it.
Widget loadingDialog = new AlertDialog(
content: new Row(
children: <Widget>[
new CircularProgressIndicator(),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: new Text("Loading..."),
),
],
),);
But, if the user taps outside the Dialog it closes. So when the auth finishes, it will still pop something (i guess the scaffol), breaking the app.
How can i make Dialog not closable?
There is a property inside showDialog called barrierDismissible. Setting this value to false will make your AlertDialog not closable by clicking outside.
showDialog(
...
barrierDismissible: false,
...
To prevent dismissing of dialog on press of a back button, you should also wrap your AlertDialog (or any other widget) inside a WillPopScope.
showDialog(
context: context,
barrierDismissible: false, // <-- Set this to false.
builder: (_) => WillPopScope(
onWillPop: () async => false, // <-- Prevents dialog dismiss on press of back button.
child: AlertDialog(...),
),
);
// User Defined Function
void _showloding() {
// flutter defined function
showDialog(
barrierDismissible: false, // JUST MENTION THIS LINE
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
content: Padding(
padding: const EdgeInsets.all(20.0),
child: Container(height: 100,width: 100,child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: SizedBox(height: 50,width: 50,child: CircularProgressIndicator())),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Center(child: Text("Processing")),
)
],
)),
)
);
},
);
}
If you want to prevent dialog close when back button pressed then refer below code. You need to wrap the AlertDialog in WillPopScope widget and make onWillPop property value with function which return Future.value(false).
showDialog(
barrierDismissible: false, //this will prevent popup closing when touch outside of popup
context: context,
builder: (BuildContext context) {
return WillPopScope( // Wrap in WillPopScope widget
onWillPop: () => Future.value(false), //this line will prevent popup closing when back button of phone press
child:AlertDialog(
title: new Text("Alert Title"),
content: new SingleChildScrollView(
child: Container(),),
actions: [
new FlatButton(
child: new Text("Close"),
onPressed: () {
},
),
],
)
)
},
);