UPDATE: recommended approach by Flutter (as of 20.12.2022)...
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.
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.')
));
}
);
}
)
);
}