Scaffold.of() 使用不包含脚手架的上下文调用

Scaffold.of() called with a context that does not contain a Scaffold

如您所见,我的按钮在 Scaffold 的主体内。但我得到这个例外:

Scaffold.of() 使用不包含脚手架的上下文调用。

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);
}

编辑:

我找到了这个问题的另一个解决方案。如果我们给 Scaffold 一个键,即 GlobalKey<ScaffoldState>,我们可以如下显示 SnackBar,而不需要将我们的主体包裹在 Builder 小部件中。 returns Scaffold 的小部件应该是有状态的小部件。

 _scaffoldKey.currentState.showSnackBar(snackbar); 

发生此异常是因为您正在使用实例化 Scaffold 的小部件的 context。不是 Scaffold 的 child 的 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'),
            ),
        ),
    ),
);

请注意,虽然我们在这里使用 Builder,但这并不是获得不同 BuildContext 的唯一方法。

也可以将子树提取到不同的Widget(通常使用extract widget重构)

使用ScaffoldMessenger(推荐)

var snackBar = SnackBar(content: Text('Hi there'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);

示例(没有 BuilderGlobalKey

Scaffold(
  body: ElevatedButton(
    onPressed: () {
      var snackBar = SnackBar(content: Text('Hello World'));
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
    },
    child: Text('Show SnackBar'),
  ),
)

您可以使用 GlobalKey。唯一的缺点是使用 GlobalKey 可能不是执行此操作的最有效方法。

这样做的好处是您还可以将此密钥传递给其他不包含任何脚手架的自定义小部件 class。参见(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
  }
}

更新 - 2021

Scaffold.of(context) is deprecated in favor of ScaffoldMessenger.

从方法的文档中检查:

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!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

您可以查看详细的弃用和新方法here:

解决此问题的简单方法是使用以下代码为您的脚手架创建一个密钥:

第一个:GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

第二步:将密钥分配给您的脚手架key: _scaffoldKey

第三种:调用Snackbar使用 _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

您可以通过两种方式解决这个问题:

1) 使用生成器小部件

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) 使用 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);
          },
        ),
     );
   }
}

一个更有效的解决方案是将构建函数拆分为多个小部件。 这里介绍一个'new context',从中可以获得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'),
              ),
            );
          }),
    );
  }
}

2021 年 3 月 3 日更新:Flutter 2.0 刚刚发布...

(作为历史价值,我也让我的原始答案更进一步...)


现在...输入...

ScaffoldMessenger

the docu我们读到

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

所以,现在使用全新的 ScaffoldMessenger,您将能够编写类似

的代码
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'),
  ),
);

现在,我们再次在文档中看到

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.


原始答案

您正在经历的行为在 Flutter documentation. 中甚至被称为“棘手案例”

如何修复

正如您从此处发布的其他答案中看到的那样,该问题已通过不同方式得到解决。例如,我参考的文档通过使用 Builder 来解决问题,它创建

an inner BuildContext so that the onPressed methods can refer to the Scaffold with Scaffold.of().

因此,从 脚手架 调用 showSnackBar 的方法是

@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.')
            ));
          }
        );
      }
    )
  );
}

现在为好奇的人提供一些细节reader

我自己发现通过简单地 (Android Studio) 探索 Flutter 文档很有启发性将光标放在一段代码上(Flutterclass、方法等)并按 ctrl+B 显示该特定片段的文档。

BuildContext 的文档中提到了您面临的特定问题,可在此处阅读

Each widget has its own BuildContext, which becomes the parent of the widget returned by the [...].build function.

所以,这意味着在我们的例子中 context 将是 我们的脚手架小部件 的父级创建 (!)。此外, Scaffold.of 的文档说 returns

The state from the closest [Scaffold] instance of this class that encloses the given context.

但在我们的例子中,context 没有包含(还)一个 Scaffold(它还没有被构建)。这就是 Builder 发挥作用的地方!

纪录片再一次照亮了我们。在那里我们可以阅读

[The Builder class, is simply] A platonic widget that calls a closure to obtain its child widget.

嘿,等一下,什么!?好吧,我承认:这并没有多大帮助......但足以说明(在another SO thread之后)

The purpose of the Builder class is simply to build and return child widgets.

所以现在一切都清楚了!通过在 Scaffold 中调用 Builder 我们正在构建 Scaffold 以便能够获得它自己的上下文,并使用 innerContext 我们终于可以调用 Scaffold.of(innerContext)

上面代码的注释版本如下

@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.')
            ));
          }
        );
      }
    )
  );
}

我不会费心使用默认的 snackbar,因为您可以导入 flushbar 包,从而实现更大的可定制性:

https://pub.dev/packages/flushbar

例如:

Flushbar(
                  title:  "Hey Ninja",
                  message:  "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
                  duration:  Duration(seconds: 3),              
                )..show(context);

这里我们使用一个构建器来包装我们需要 snackbar 的另一个小部件

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))

试试这个代码:

Singleton.showInSnackBar(
    Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!

从 Flutter 1.23-18 版本开始。1.pre 您可以使用 ScaffoldMessenger

final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

class Main extends StatelessWidget {
  @override
  Widget build(BuildContext) {
    return MaterialApp(
      ...
      scaffoldMessengerKey: mainScaffoldMessengerKey
      ...
    );
  }
}

应用内某处:

mainScaffoldMessengerKey.currentState.showSnackBar(Snackbar(...));

我可能要迟到了。但这也会帮助某人。 在脚手架下添加一个_key。 然后使用 _key 调用 openDrawer 方法。

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

也许这段代码中有解决方案。

ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SnackBar Message")));