Reputation: 12559
As you can see, my button is inside the Scaffold
's body. But I get this exception:
Scaffold.of() called with a context that does not contain a Scaffold.
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: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SnackBar Playground'),
),
body: Center(
child: RaisedButton(
color: Colors.pink,
textColor: Colors.white,
onPressed: _displaySnackBar(context),
child: Text('Display SnackBar'),
),
),
);
}
}
_displaySnackBar(BuildContext context) {
final snackBar = SnackBar(content: Text('Are you talkin\' to me?'));
Scaffold.of(context).showSnackBar(snackBar);
}
EDIT:
I found another solution to this problem. If we give the Scaffold
a key which is the GlobalKey<ScaffoldState>
, we can display the SnackBar as following without the need to wrap our body within the Builder
widget. The widget which returns the Scaffold
should be a Stateful widget though.
_scaffoldKey.currentState.showSnackBar(snackbar);
Upvotes: 372
Views: 162242
Reputation: 73
for me I had to wrap my widget with Scaffold
bonus tip: backgroundColor:Colors.transparnt
Upvotes: 0
Reputation: 7445
To show a Snackbar you should be using:
From the docu we read
The SnackBar API within the Scaffold is now handled by the ScaffoldMessenger, one of which is available by default within the context of a MaterialApp
So, with ScaffoldMessenger now you will be able to write code like
Scaffold(
key: scaffoldKey,
body: GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('snack'),
duration: const Duration(seconds: 1),
action: SnackBarAction(
label: 'ACTION',
onPressed: () { },
),
));
},
child: const Text('SHOW SNACK'),
),
);
Now, again in the docu we can see that
When presenting a SnackBar during a transition, the SnackBar will complete a Hero animation, moving smoothly to the next page.
The ScaffoldMessenger creates a scope in which all descendant Scaffolds register to receive SnackBars, which is how they persist across these transitions. When using the root ScaffoldMessenger provided by the MaterialApp, all descendant Scaffolds receive SnackBars, unless a new ScaffoldMessenger scope is created further down the tree. By instantiating your own ScaffoldMessenger, you can control which Scaffolds receive SnackBars, and which do not based on the context of your application.
The very behavior you are experiencing is even referred to as a "tricky case" in the Flutter documentation.
The issue is fixed in different ways as you can see from other answers posted here. For instance, the piece of documentation i refer to solves the issue by using a Builder
which creates
an inner
BuildContext
so that theonPressed
methods can refer to theScaffold
withScaffold.of()
.
Thus a way to call showSnackBar
from Scaffold would be
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo')),
body: Builder(
builder: (BuildContext innerContext) {
return FlatButton(
child: Text('BUTTON'),
onPressed: () {
Scaffold.of(innerContext).showSnackBar(SnackBar(
content: Text('Hello.')
));
}
);
}
)
);
}
I myself found quite instructive to explore the Flutter documentation by simply (Android Studio) setting the cursor on a piece of code (Flutter class, method, etc.) and pressing ctrl+B to be shown the documentation for that specific piece.
The particular problem you are facing is mentioned in the docu for BuildContext, where can be read
Each widget has its own BuildContext, which becomes the parent of the widget returned by the [...].build function.
So, this means that in our case context will be the parent of our Scaffold widget when it is created (!). Further, the docu for Scaffold.of says that it returns
The state from the closest [Scaffold] instance of this class that encloses the given context.
But in our case, context does not encloses (yet) a Scaffold (it has not yet been built). There is where Builder comes into action!
Once again, the docu illuminates us. There we can read
[The Builder class, is simply] A platonic widget that calls a closure to obtain its child widget.
Hey, wait a moment, what!? Ok, i admit: that is not helping a lot... But it is enough to say (following another SO thread) that
The purpose of the Builder class is simply to build and return child widgets.
So now it all becomes clear! By calling Builder inside Scaffold we are building the Scaffold in order to be able to obtain its own context, and armed with that innerContext we can finally call Scaffold.of(innerContext)
An annotated version of the code above follows
@override
Widget build(BuildContext context) {
// here, Scaffold.of(context) returns null
return Scaffold(
appBar: AppBar(title: Text('Demo')),
body: Builder(
builder: (BuildContext innerContext) {
return FlatButton(
child: Text('BUTTON'),
onPressed: () {
// here, Scaffold.of(innerContext) returns the locally created Scaffold
Scaffold.of(innerContext).showSnackBar(SnackBar(
content: Text('Hello.')
));
}
);
}
)
);
}
Upvotes: 19
Reputation: 9257
Scaffold.of(context)
is deprecated in favor ofScaffoldMessenger
.
Check this from the documentation of method:
The ScaffoldMessenger now handles SnackBars in order to persist across routes and always be displayed on the current Scaffold. By default, a root ScaffoldMessenger is included in the MaterialApp, but you can create your own controlled scope for the ScaffoldMessenger to further control which Scaffolds receive your SnackBars.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
),
);
},
),
);
}
You can check the detailed deprecation and new approach here:
Upvotes: 35
Reputation: 17088
You can solve this problem in two ways:
1) Using Builder widget
Scaffold(
appBar: AppBar(
title: Text('My Profile'),
),
body: Builder(
builder: (ctx) => RaisedButton(
textColor: Colors.red,
child: Text('Submit'),
onPressed: () {
Scaffold.of(ctx).showSnackBar(SnackBar(content: Text('Profile Save'),),);
}
),
),
);
2) Using GlobalKey
class HomePage extends StatelessWidget {
final globalKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
appBar: AppBar(
title: Text('My Profile'),
),
body: RaisedButton(
textColor: Colors.red,
child: Text('Submit'),
onPressed: (){
final snackBar = SnackBar(content: Text('Profile saved'));
globalKey.currentState.showSnackBar(snackBar);
},
),
);
}
}
Upvotes: 91
Reputation: 267404
ScaffoldMessenger
(Recommended)var snackBar = SnackBar(content: Text('Hi there'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Builder
or GlobalKey
)Scaffold(
body: ElevatedButton(
onPressed: () {
var snackBar = SnackBar(content: Text('Hello World'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: Text('Show SnackBar'),
),
)
Upvotes: 15
Reputation: 53
Maybe there is a solution in this code.
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SnackBar Message")));
Upvotes: 5
Reputation: 51
Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Builder(
builder: (context) => RaisedButton(
onPressed: () {
final snackBar = SnackBar(
content: Text("added to cart"),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
},
textColor: Colors.white,
color: Colors.pinkAccent,
child: Text("Add To Cart",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w600)),
),
),
),
)
Upvotes: 3
Reputation: 1114
I may be late. But this will help someone too. Add a _key under the Scaffold. Then use that _key to call the openDrawer method.
return Scaffold(
key: _scaffoldKey, //this is the key
endDrawer: Drawer(),
appBar: AppBar(
//all codes for appbar here
actions: [
IconButton(
splashRadius: 20,
icon: Icon(Icons.settings),
onPressed: () {
_scaffoldKey.currentState.openEndDrawer(); // this is it
},
),]
Upvotes: 6
Reputation: 7100
From Flutter version 1.23-18.1.pre you can use ScaffoldMessenger
final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
class Main extends StatelessWidget {
@override
Widget build(BuildContext) {
return MaterialApp(
...
scaffoldMessengerKey: mainScaffoldMessengerKey
...
);
}
}
Somewhere inside app:
mainScaffoldMessengerKey.currentState.showSnackBar(Snackbar(...));
Upvotes: 12
Reputation: 11
Try this code:
Singleton.showInSnackBar(
Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!
Upvotes: 0
Reputation: 31
here we use a builder to wrap in another widget where we need snackbar
Builder(builder: (context) => GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Your Services have been successfully created Snackbar'),
));
},
child: Container(...)))
Upvotes: 3
Reputation: 192
I wouldn't bother using the default snackbar, because you can import a flushbar package, which enables greater customizability:
https://pub.dev/packages/flushbar
For example:
Flushbar(
title: "Hey Ninja",
message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
duration: Duration(seconds: 3),
)..show(context);
Upvotes: 5
Reputation: 2522
A more efficient solution is to split your build function into several widgets. This introduce a 'new context', from which you can obtain Scaffold
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Scaffold.of example.')),
body: MyScaffoldBody(),
),
);
}
}
class MyScaffoldBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text('Show a snackBar'),
onPressed: () {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Have a Snack'),
),
);
}),
);
}
}
Upvotes: 7
Reputation: 3748
You can use a GlobalKey
. The only downside is that using GlobalKey might not be the most efficient way of doing this.
A good thing about this is that you can also pass this key to other custom widgets class that do not contain any scaffold. See(here)
class HomePage extends StatelessWidget {
final _scaffoldKey = GlobalKey<ScaffoldState>(); \\ new line
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey, \\ new line
appBar: AppBar(
title: Text('SnackBar Playground'),
),
body: Center(
child: RaisedButton(
color: Colors.pink,
textColor: Colors.white,
onPressed: _displaySnackBar(context),
child: Text('Display SnackBar'),
),
),
);
}
_displaySnackBar(BuildContext context) {
final snackBar = SnackBar(content: Text('Are you talkin\' to me?'));
_scaffoldKey.currentState.showSnackBar(snackBar); \\ edited line
}
}
Upvotes: 200
Reputation: 190
Simple way to solving this issue will be creating a key for your scaffold like this final with the following code:
First: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState>
();
Scecond: Assign the Key to your Scaffold key: _scaffoldKey
Third: Call the Snackbar using
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));
Upvotes: 19
Reputation: 276891
This exception happens because you are using the context
of the widget that instantiated Scaffold
. Not the context
of a child of Scaffold
.
You can solve this by just using a different context :
Scaffold(
appBar: AppBar(
title: Text('SnackBar Playground'),
),
body: Builder(
builder: (context) =>
Center(
child: RaisedButton(
color: Colors.pink,
textColor: Colors.white,
onPressed: () => _displaySnackBar(context),
child: Text('Display SnackBar'),
),
),
),
);
Note that while we're using Builder
here, this is not the only way to obtain a different BuildContext
.
It is also possible to extract the subtree into a different Widget
(usually using extract widget
refactor)
Upvotes: 473