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(),
[...]
Related
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
So I have a drawer and I can open it with the hamburger icon on top-left corner. If I change the screen to another page, this hamburger icon become a back button (<--). How to make this into a hamburger icon again? (So no back button but open the drawer on every screen)
In App Bar of next page put
leading: BurgerIcon(),
The Burger icon is from having a Drawer on your Scaffold.
If your next page/route has a Scaffold with a drawer: specified, you'll get the "burger" icon button again.
Without a drawer: specified on Scaffold Flutter will default to the back arrow to go to the previous route.
Here's an example:
import 'package:flutter/material.dart';
class DrawerDirectoryPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Drawer Directories'),
),
body: Center(
child: InkWell(
child: Text('Page One. Click to go to Page Two.'),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PageTwo())
),
),
),
drawer: MyDrawerDirectory(),
);
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Burger Time!'),
),
body: Center(
child: Text('Page TWO /flex'),
),
drawer: MyDrawerDirectory(), // ← Drawer Directory a.k.a. burger icon
);
}
}
class MyDrawerDirectory extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: [
ListTile(title: Text('Page One'), onTap: () => _navPush(context, DrawerDirectoryPage())),
ListTile(title: Text('Page Two'), onTap: () => _navPush(context, PageTwo()))
],
),
);
}
Future<dynamic> _navPush(BuildContext context, Widget page) {
return Navigator.push(context, MaterialPageRoute(
builder: (context) => page,
));
}
}
In the AppBar widget, you can specify a leading widget. After you specify a leading widget (in your case, an Icon Button), you should set AppBar's automaticallyImplyLeading = false.
If you use the methods Navigator.of(context).push or Navigator.of(context).pushNamed you will also experience this issue.
You can use other method to navigate between screens so it doesn't show the hamburger button
Put automaticallyImplyLeading: false in appBar of next page then set leading icon like this:
automaticallyImplyLeading: false,
leading: Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () {
if (scaffoldKey.currentState.isDrawerOpen) {
Navigator.of(context).pop();
} else {
scaffoldKey.currentState.openDrawer();
}
},
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Icon(
Icons.menu,
size: 30,
color: appColor,
),
),
),
),
I have a drawer class in common for 3 pages. Here my problem is if i open any list item page in drawer while in main screen its working fine. if i goto some page(say page2 screen) other than main screen, now if i open any list item in drawer it opens the screen but problem is if i press back button it goes to main screen instead of page2 screen.
Drawer class:
class CustomDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.deepOrangeAccent.withOpacity(0.9), //This will change the drawer background to blue.//other styles
),
child: Drawer(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left:8.0),
child: ListView(
shrinkWrap: true,
children: <Widget>[
ListTile(
leading: Icon(Icons.event_note,color: Colors.white),
title: Text("Menu",style: TextStyle(color:Colors.white),),
onTap: () {
Navigator.push (
context, MaterialPageRoute(builder(BuildContextcontext)=> Menu()));
},
),
ListTile(
leading: Icon(Icons.person,color: Colors.white),
title: Text("My Profile",style: TextStyle(color:Colors.white),),
onTap: () {
Navigator.push (
context, MaterialPageRoute(builder: (BuildContext context) => MyProfile()));
},
),
],
),
)
],
),
),
);
}
}
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.
I want a popup menu or some kind of slide screen with options to come when i click on an icon in the app bar, however i dont want to use PopMenuButton as i dont want to use that icon. How can I do this?
My code
return new Scaffold(
appBar: new AppBar(
title: new Text("Home"),
leading: IconButton(
icon: Icon(
Icons.dehaze,
color: Colors.black,
),
onPressed: () {
// do something
},
),
),
body: new Center(...),
);
#Denise, you don't need to manually create a button and assign action for drawer menu. You can simply use drawer in Scaffold with Drawer widget like so,
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Test'),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
body: Padding(
padding: EdgeInsets.all(20.0),
child: Center(
child: Column(
children: <Widget>[
Text('')
],
)
)
),
)
);
}
}
And if you wanna use different icon,
class MyAppState extends State<MyApp> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Test'),
leading: new IconButton(
icon: new Icon(Icons.dehaze),
onPressed: () => _scaffoldKey.currentState.openDrawer()),
),
drawer: Drawer(......
Hope this helps.
If the icon is the problem in PopMenuButton. You can change it by assigning icon attribute in PopMenuButton.
PopupMenuButton<Choice>(
onSelected: _select,
icon:Icon(
Icons.dehaze,
color: Colors.black,
),
itemBuilder: (BuildContext context) {
return choices.skip(2).map((Choice choice) {
return PopupMenuItem<Choice>(
value: choice,
child: Text(choice.title),
);
}).toList();
https://flutter.dev/docs/catalog/samples/basic-app-bar