'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