Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 996
Prashant naik Avatar asked Sep 08 '25 03:09

Prashant naik


2 Answers

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.


1 The 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()

like image 152
Baker Avatar answered Sep 09 '25 16:09

Baker


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())
            ],
          ),
        ),
      ),
    );
  }
}

like image 42
Prashant naik Avatar answered Sep 09 '25 18:09

Prashant naik