'Flutter : No ScaffoldMessenger widget found
I am trying to create a snackbar on the click of a button in flutter, but getting exception that No ScaffoldMessenger
widget found. The same code seems to work properly in the Flutter sample mentioned in docs. Am I missing something here? Thanks.
Here's my main.dart file
import 'package:flutter/material.dart';
void main() => runApp(MyAppWidget());
class MyAppWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyAppWidget> {
final _inputKey = GlobalKey<FormState>();
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
String inputText = "";
String appendString() {
setState(() {
inputText += inputText;
});
return inputText;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
key: _inputKey,
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_inputKey.currentState.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Processing Data')));
}
},
child: Text("Enter"),
),
Text(appendString())
],
),
),
),
);
}
}
Here's the exception I am getting
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No ScaffoldMessenger widget found.
MyAppWidget widgets require a ScaffoldMessenger widget ancestor.
Handler: "onTap"
#4 _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:991
#3 _MyAppState.build.<anonymous closure>
package:assignment_1/main.dart:48
#2 ScaffoldMessenger.of
package:flutter/…/material/scaffold.dart:224
#1 debugCheckHasScaffoldMessenger
package:flutter/…/material/debug.dart:153
#0 debugCheckHasScaffoldMessenger.<anonymous closure>
package:flutter/…/material/debug.dart:142
When the exception was thrown, this was the stack
Typically, the ScaffoldMessenger widget is introduced by the MaterialApp at the top of your application widget tree.
renderObject: RenderView#a5074
[root]
The ancestors of this widget were
state: _MyAppState#a2cb9
Restarted application in 691ms.
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No ScaffoldMessenger widget found.
MyAppWidget widgets require a ScaffoldMessenger widget ancestor.
The specific widget that could not find a ScaffoldMessenger ancestor was: MyAppWidget
Solution 1:[1]
scaffoldMessengerKey.currentState.showSnackBar(mySnackBar); scaffoldMessengerKey.currentState.hideCurrentSnackBar(mySnackBar); scaffoldMessengerKey.currentState.removeCurrentSnackBar(mySnackBar);
Solution 2:[2]
as suggested by EngineSense, Created one Global key
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
and added this in the onPressed method of button
_messangerKey.currentState.showSnackBar(
SnackBar(content: Text('Processing Data')));
Here's the updated changes for reference.
import 'package:flutter/material.dart';
void main() => runApp(MyAppWidget());
class MyAppWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyAppWidget> {
final _inputKey = GlobalKey<FormState>();
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
String inputText = "";
String appendString() {
setState(() {
inputText += inputText;
});
return inputText;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
key: _inputKey,
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_inputKey.currentState.validate()) {
_messangerKey.currentState.showSnackBar(
SnackBar(content: Text('Processing Data')));
}
},
child: Text("Enter"),
),
Text(appendString())
],
),
),
),
);
}
}
Solution 3:[3]
The Issue / Why this happens
To answer the original question: "What am I missing here?":
ScaffoldMessenger.of(context)
is using a context
not underneath (i.e. not a child of) MaterialApp
. (And there is no ScaffoldMessenger
to be found above.)
Native Flutter static methods <SomeClassName>.of(context)
walk up the widget tree looking for an InheritedWidget
type parent named <SomeClassName>
. So ScaffoldMessenger.of(context)
is looking for a ScaffoldMessenger
parent.1
MaterialApp
widget provides us a ScaffoldMessenger
because it wraps its children in a ScaffoldMessenger
. Here's an excerpt of MaterialApp's
"builder" method:
Widget _materialBuilder(BuildContext context, Widget? child) {
return ScaffoldMessenger( // <<<< <<<< hello Mr. ScaffoldMessenger
key: widget.scaffoldMessengerKey,
child: AnimatedTheme(
data: theme,
child: widget.builder != null
? Builder(
builder: (BuildContext context) {
return widget.builder!(context, child); // your kid
},
)
: child ?? const SizedBox.shrink(),
),
);
}
The child
above is likely your top-most widget that you provided to MaterialApp
. Only that widget's build method and below will find the ScaffoldMessenger
from MaterialApp
in its parent ancestry.
The original question's .showSnackBar()
was looking up MyApp's
context / parent ancestry, not MaterialApp's
.
MyApp(context)
-> MaterialApp(gets MyApp context) + other Widget(gets MyApp context) (no ScaffoldMessenger avail!)
-> kids (gets MaterialApp context, thus ScaffoldMessenger above)
Code from Original Question
It's really easy to mistake which context
we're using.
In the original code snippet, we're using the closest visible context
at this level, which is MyApp's
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) { // <<<< this MyApp context...
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
key: _inputKey,
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_inputKey.currentState.validate()) {
ScaffoldMessenger.of(context).showSnackBar( // is this context <<<
SnackBar(content: Text('Processing Data')));
}
Even though the Scaffold > Form > ElevatedButton
widgets appear physically "lower/underneath" MaterialApp
, they're currently within MyApp's build(BuildContext context)
method and thus are currently using MyApp's
context
.
To use the context of MaterialApp
, Scaffold/Form/ElevatedButton
need to be called from inside a build(BuildContext context)
method that's inside MaterialApp
& thus gets its context
.
One way to avoid the above pitfall is to keep MyApp/MaterialApp
very simple and start our coding another level down, say in HomePage
in the example below:
class MyApp extends StatelessWidget {
const MyApp({Key? key, required this.bindings}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(), // <<< put your stuff in HomePage's build() method
);
}
}
Alternatively we can wrap the home:
widget in a Builder
widget (docs) and every child within that Builder
will then be using MaterialApp's context
, which has the first available ScaffoldMessenger
.
class MyApp extends StatelessWidget {
const MyApp({Key? key, required this.bindings}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(builder: (BuildContext context) {
// stuff here gets MaterialApp context
}),
);
}
}
And as mentioned in other answers we can use a scaffoldMessengerKey
arg using a GlobalKey<ScaffoldMessengerState>
as described in the migration guide when ScaffoldMessenger
was introduced.
1The InheritedWidget
type looked up by this .of()
is actually _ScaffoldMessengerScope
, which contains the ScaffoldMessengerState
field, which is the object providing the SnackBar
API methods we're interested in, such as .showSnackBar()
Solution 4:[4]
The Flutter gallery on GitHub shows that the feature is going to get an upgrade and it will be updated when they merge their flutter master branch again with the updated one. For now, you can use Scaffold only or implement it as a key after writing manual code for it in a key variable.
Solution 5:[5]
I met the same issue, and found a strange behavior.
Below code is copy from Display a snackbar, it worked well
import 'package:flutter/material.dart';
void main() => runApp(const SnackBarDemo());
class SnackBarDemo extends StatelessWidget {
const SnackBarDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SnackBar Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('SnackBar Demo'),
),
body: const SnackBarPage(),
),
);
}
}
class SnackBarPage extends StatelessWidget {
const SnackBarPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: const Text('Show SnackBar'),
),
);
}
}
However, when I remove the SnackBarPage
class, and copy it’s code in build()
method to the SnackBarDemo
class directly, something like below.
import 'package:flutter/material.dart';
void main() => runApp(const SnackBarDemo());
class SnackBarDemo extends StatelessWidget {
const SnackBarDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SnackBar Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('SnackBar Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: const Text('Show SnackBar'),
),
)),
);
}
}
Then it will NOT work any more, and raise No ScaffoldMessenger widget found. SnackBarDemo widgets require a ScaffoldMessenger widget ancestor.
, It seems that ScaffoldMessenger
only work when wrapped in some Widget
class. I don't known why.
Solution 6:[6]
what worked for me was to have a seperated StatefulWidget for The Scaffold() (as below MyScaffold). Its working with me.
import 'package:flutter/material.dart';
void main() => runApp(MyAppWidget());
class MyAppWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyAppWidget> {
@override
Widget build(BuildContext context) {
return MaterialApp(home: MyScaffold());
}
}
class MyScaffold extends StatefulWidget {
const MyScaffold({Key? key}) : super(key: key);
@override
_MyScaffoldState createState() => _MyScaffoldState();
}
class _MyScaffoldState extends State<MyScaffold> {
String inputText = "";
String appendString() {
setState(() {
inputText += inputText;
});
return inputText;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString!;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
},
child: Text("Enter"),
),
Text(appendString())
],
),
),
);
}
}
This is my first answer so i don't know a lot about formatting.
Solution 7:[7]
You can make the SnackBarGlobal a class and make it static so you can use it throughout your application.
This is a small example where you change the navigation in different widgets and in each of them you can fire the SnackBarGlobal. view on DartPad
You can also see the documentation where it explains the error Scaffold widgets require a ScaffoldMessenger widget ancestor
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: SnackbarGlobal.key,
routes: routes,
initialRoute: 'pageOne',
);
}
}
class SnackbarGlobal {
static GlobalKey<ScaffoldMessengerState> key =
GlobalKey<ScaffoldMessengerState>();
static void show(String message) {
key.currentState!
..hideCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(message)));
}
}
class MyPage extends StatelessWidget {
final String title;
final String route;
MyPage(this.title, this.route);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
child: Text('Go to $route'),
onPressed: () {
Navigator.pushNamed(context, route);
},
),
TextButton(
child: Text('Show SnackBar in $title'),
onPressed: () {
SnackbarGlobal.show('This is a SnackBar in $title');
},
),
],
),
),
);
}
}
final routes = <String, WidgetBuilder>{
'pageOne': (BuildContext context) => MyPage('My Page One', 'pageTwo'),
'pageTwo': (BuildContext context) => MyPage('My Page Two', 'pageThree'),
'pageThree': (BuildContext context) => MyPage('My Page Three', 'pageOne'),
};
Solution 8:[8]
Use This:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: _content ,duration: Duration(milliseconds: 200),));
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | EngineSense |
Solution 2 | Prashant naik |
Solution 3 | Baker |
Solution 4 | Gurshehzad Singh |
Solution 5 | crazygit |
Solution 6 | Dharman |
Solution 7 | |
Solution 8 | Parichit Patel |