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);
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)
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
}
}
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);
},
),
);
}
}
UPDATE - 2021
Scaffold.of(context) is deprecated in favor of ScaffoldMessenger.
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:
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")));
UPDATE: recommended approach by Flutter (as of 20.12.2022)...
To show a Snackbar you should be using:
ScaffoldMessenger
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.
ORIGINAL ANSWER
The very behavior you are experiencing is even referred to as a "tricky case" in the Flutter documentation.
How To Fix
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 the onPressed methods can refer to the Scaffold with Scaffold.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.')
));
}
);
}
)
);
}
Now some detail for the curious reader
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.')
));
}
);
}
)
);
}
Use ScaffoldMessenger (Recommended)
var snackBar = SnackBar(content: Text('Hi there'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Example (Without Builder or GlobalKey)
Scaffold(
body: ElevatedButton(
onPressed: () {
var snackBar = SnackBar(content: Text('Hello World'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: Text('Show SnackBar'),
),
)
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(...));
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'),
),
);
}),
);
}
}
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
},
),]
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);
Maybe there is a solution in this code.
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SnackBar Message")));
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)),
),
),
),
)
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(...)))
Try this code:
Singleton.showInSnackBar(
Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!
Related
I have a Home page for an ecommerce website and I am showing every product in image format on click of every product(image) I want that image to be filled up in next screen which is of info screen of product that also contains the product (image) and some other related stuff. I want to build info page only a single time and just change the product image and other stuff accordingly as per the image (product) being clicked. How can I achieve this I am new to flutter and dont know this logic i came across valuelistenablebuilder but not getting how can i achieve this, Please Help.
Thank you,
By default, Flutter is providing this support by using the Hero widget. Find the code snippets below.
import 'package:flutter/material.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Transition Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
),
);
}
}
class DetailScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
Make sure the Hero.tag property should be maintained as same for both pages.
Refer the link for more details
I declared a class, returned MaterialApp and used button in it also used Navigator.Push method for navigation to different page, but it gave exception
Navigator operation requested with a context that does not include a
Navigator
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
SafeArea(
child: Scaffold(
backgroundColor: Colors.red[100],
body:Column(
children: [
SizedBox(height: 50,),
Align(
alignment:Alignment.center,
child:Image.asset("assets/icons/appicon.png",
height:150),
),
Align(
alignment: Alignment.bottomCenter,
child: RaisedButton(
child: Text('Click Picture'),
color: Colors.red[800],
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CameraRoute()
),
);
},
),
)
],
),
),
)
);
//throw UnimplementedError();
}
}
Separate the page/screen from MyApp by creating a new widget.
Like so
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
SizedBox(height: 100),
Image.asset(
"assets/icons.appicon.png",
height: 150,
),
RaisedButton(
child: Text("Click Picture"),
color: Colors.red,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CameraRoute(),
),
);
},
),
],
),
),
),
);
}
}
You're trying to get Navigator from a context that doesn't have a Navigator as parent. The only navigator in your code is in the MaterialApp, but that is a child of context.
The minimal way of getting a context that is a child of MaterialApp is to wrap your Scaffold with a Builder.
A better approach would be to split your code into more widgets (the build-method of each widget exposes a context) as mentioned by the other answer.
I've been trying flutter new declarative Navigator v2.0, following this
johnpryan example, I've decide to change FlatButton pop() in the BookDetailsScreen:
class BookDetailsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
children: [
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Back'),
),
],
}
To the parent router AppBar() in the _AppShellState:
class _AppShellState extends State<AppShell> {
InnerRouterDelegate _routerDelegate;
ChildBackButtonDispatcher _backButtonDispatcher;
#override
Widget build(BuildContext context) {
var appState = widget.appState;
_backButtonDispatcher.takePriority();
return Scaffold(
appBar: appState.selectedBook != null
? AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => _routerDelegate.popRoute(),
),
actions: [
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () => null,
)
],
)
: AppBar(),
body: Router(
routerDelegate: _routerDelegate,
backButtonDispatcher: _backButtonDispatcher,
),
Fullcode
My question is, I'm using _routerDelegate.popRoute() it's working, but I not sure if it is right way to do it?
PS: If someone have a more complex example using Navigator v2.0, I'm new to Flutter and need to know best practices to how separate and organize my code, how to add more routes for instance an Edit and login screen? working with more objects like users, books, authors and etc...
I have a tweak to show the back button, kinda naturally, when the BookDetailsScreen is presented.
In the _AppShellState class
#override
Widget build(BuildContext context) {
...
return Scaffold(
// <- Comment out this appBar
// appBar: AppBar(
// leading: appState.selectedBook != null
// ? IconButton(
// icon: Icon(Icons.arrow_back),
// onPressed: () => _routerDelegate.popRoute(),
// )
// : null,
// ),
...
// Keep the rest part the same
);
}
Add the appBar property into Screens Scaffold in the build method, like this below. Have the appBar on both BooksListScreen and BookDetailsScreen,
class BooksListScreen extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(), // <- This is the key
...
);
}
}
Hope this makes sense.
I tried using the following function inside build body.But it throws error saying
The argument type 'Future<void> Function(BuildContext)' can't be assigned to the parameter type 'void Function()'
Future<void> confirmation(BuildContext context) async {
return await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.black54,
content: Center(
child: Text(
"Please Wait....",
style: TextStyle(color: Colors.blueAccent),
)));
});
}
class Trial extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text('Request Processed')),
body: Center(
child: Flatbutton(onPressed: confirmation,child: Text('Click me')), //this onpressed shows error
),
),
);
}
}
I have tried calling the same function from appBar action widget icon and it didn't throw any error.On using in build function only it throws error. Why is that so?
Try to call it like this
class Trial extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text('Request Processed')),
body: Center(
child: Flatbutton(onPressed: () {confirmation(context);},child: Text('Click me')), //this onpressed shows error
),
),
);
}
}
It seems the same but it saved me a lot of time. Let me know:)
Pass the context while calling it. Like this ()=>confirmation(context)
class Trial extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text('Request Processed')),
body: Center(
child: Flatbutton(onPressed: ()=>confirmation(context),child: Text('Click me')), //this onpressed shows error
),
),
);
}
}
This question already has answers here:
Scaffold.of() called with a context that does not contain a Scaffold
(15 answers)
Closed 2 years ago.
I can't seem to figure out why this isn't working. I'm supposed to get a snackbar when I tap the button. Any help? The code snippet on flutter api works fine tho
void main() => runApp(SnackBarDemo());
class SnackBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Center(
child: InkWell(
// When the user taps the button, show a snackbar.
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Tap'),
));
},
child: Container(
padding: EdgeInsets.all(12.0),
child: Text('Flat Button'),
),
),
)
)
)
);
}
}
You are calling showSnackbar in a widget on the same level of the Scaffold, so the context you are sending to this method does not contain a Scaffold,
Soltuion:
make this widget one level below the Scaffold:
void main() => runApp(SnackBarDemo());
class SnackBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Center(
child: Builder(
builder: (context)=>InkWell(
// When the user taps the button, show a snackbar.
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Tap'),
));
},
child: Container(
padding: EdgeInsets.all(12.0),
child: Text('Flat Button'),
),
),
),
)
)
)
);
}
}