简体   繁体   English

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

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

As you can see, my button is inside the Scaffold 's body.如您所见,我的按钮位于Scaffold的体内。 But I get this exception:但我得到这个例外:

Scaffold.of() called with a context that does not contain a 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);
}

EDIT:编辑:

I found another solution to this problem.我找到了解决这个问题的另一种方法。 If we give the Scaffold a key which is the GlobalKey<ScaffoldState> , we can display the SnackBar as following without the need to wrap our body within the Builder widget.如果我们给Scaffold一个键,它是GlobalKey<ScaffoldState> ,我们可以如下显示 SnackBar,而不需要将我们的主体包装在Builder小部件中。 The widget which returns the Scaffold should be a Stateful widget though.不过,返回Scaffold的小部件应该是有状态的小部件。

 _scaffoldKey.currentState.showSnackBar(snackbar); 

This exception happens because you are using the context of the widget that instantiated Scaffold .发生此异常是因为您正在使用实例化Scaffold的小部件的context Not the context of a child of Scaffold .不是Scaffold的孩子的context

You can solve this by just using a different 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'),
            ),
        ),
    ),
);

Note that while we're using Builder here, this is not the only way to obtain a different BuildContext .请注意,虽然我们在这里使用Builder ,但这并不是获取不同BuildContext的唯一方法。

It is also possible to extract the subtree into a different Widget (usually using extract widget refactor)也可以将子树提取到不同的Widget (通常使用extract widget refactor)

You can use a GlobalKey .您可以使用GlobalKey The only downside is that using GlobalKey might not be the most efficient way of doing this.唯一的缺点是使用 GlobalKey 可能不是最有效的方法。

A good thing about this is that you can also pass this key to other custom widgets class that do not contain any scaffold.这样做的一个好处是,您还可以将此密钥传递给不包含任何脚手架的其他自定义小部件类。 See( 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
  }
}

You can solve this problem in two ways:您可以通过两种方式解决此问题:

1) Using Builder widget 1) 使用 Builder 小部件

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) Using GlobalKey 2) 使用全局键

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

UPDATE - 2021更新 - 2021

Scaffold.of(context) is deprecated in favor of ScaffoldMessenger .不推荐使用Scaffold.of(context)以支持ScaffoldMessenger

Check this from the documentation of method:从方法文档中检查这一点:

The ScaffoldMessenger now handles SnackBars in order to persist across routes and always be displayed on the current Scaffold. ScaffoldMessenger现在处理SnackBars以便跨路由持久化并始终显示在当前 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.默认情况下, MaterialApp 中包含根ScaffoldMessenger ,但您可以为ScaffoldMessenger创建自己的受控范围,以进一步控制哪些 Scaffolds 接收您的 SnackBar。

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

You can check the detailed deprecation and new approach here :您可以在此处查看详细的弃用和新方法:

Simple way to solving this issue will be creating a key for your scaffold like this final with the following code:解决此问题的简单方法是使用以下代码为您的脚手架创建一个密钥,如最终版本:

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

Scecond: Assign the Key to your Scaffold key: _scaffoldKey Scecond:将密钥分配给您的 Scaffold key: _scaffoldKey

Third: Call the Snackbar using _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));第三:调用 Snackbar 使用_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

UPDATE 03.03.2021: Flutter 2.0 has just been released... 2021 年 3 月 3 日更新:Flutter 2.0 刚刚发布...

(As of historic value, i also let further below my original answer...) (就历史价值而言,我还进一步低于我的原始答案......)


Now... Enter...现在...输入...

ScaffoldMessenger 脚手架信使

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 Scaffold 中的 SnackBar API 现在由 ScaffoldMessenger 处理,其中之一在 MaterialApp 的上下文中默认可用

So, using the brand new ScaffoldMessenger now you will be able to write code like因此,现在使用全新的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'),
  ),
);

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.在过渡期间呈现 SnackBar 时,SnackBar 会完成一个 Hero 动画,平滑地移动到下一页。

The ScaffoldMessenger creates a scope in which all descendant Scaffolds register to receive SnackBars, which is how they persist across these transitions. ScaffoldMessenger 创建了一个作用域,所有后代 Scaffolds 在其中注册以接收 SnackBars,这就是它们在这些转换中持久化的方式。 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.当使用 MaterialApp 提供的根 ScaffoldMessenger 时,所有后代 Scaffolds 都会收到 SnackBars,除非在树的更下方创建新的 ScaffoldMessenger 作用域。 By instantiating your own ScaffoldMessenger, you can control which Scaffolds receive SnackBars, and which do not based on the context of your application.通过实例化您自己的 ScaffoldMessenger,您可以控制哪些 Scaffolds 接收 SnackBars,哪些不基于您的应用程序的上下文。


ORIGINAL ANSWER原答案

The very behavior you are experiencing is even referred to as a "tricky case" in the Flutterdocumentation .您所遇到的这种行为甚至在Flutter文档中被称为“棘手案例”。

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例如,一条文档编制的我用指解决问题的Builder ,其产生

an inner BuildContext so that the onPressed methods can refer to the Scaffold with Scaffold.of() .BuildContext使得onPressed方法可以参照ScaffoldScaffold.of()

Thus a way to call showSnackBar from Scaffold would be因此,从Scaffold调用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.')
            ));
          }
        );
      }
    )
  );
}

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.我自己发现通过简单地 ( Android Studio ) 将光标设置在一段代码( Flutter类、方法等)上并按 ctrl+B 以显示该特定部分的文档,我自己发现探索Flutter文档非常有指导意义。

The particular problem you are facing is mentioned in the docu for BuildContext , where can be read BuildContext的文档中提到了您面临的特定问题,可以在其中阅读

Each widget has its own BuildContext , which becomes the parent of the widget returned by the [...].build function.每个小部件都有自己的BuildContext ,它成为 [...].build 函数返回的小部件的父级。

So, this means that in our case context will be the parent of our Scaffold widget when it is created (!).因此,这意味着在我们的案例中,上下文是创建时我们的 Scaffold 小部件的父级(!)。 Further, the docu for Scaffold.of says that it returns此外, Scaffold.of的文档说它返回

The state from the closest [ Scaffold ] instance of this class that encloses the given context.来自包含给定上下文的此类的最近 [ Scaffold ] 实例的状态。

But in our case, context does not encloses (yet) a Scaffold (it has not yet been built).但是在我们的例子中,上下文没有(还)包含一个脚手架(它还没有被构建)。 There is where Builder comes into action! Builder在那里开始行动了!

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. [Builder 类,很简单] 一个柏拉图式的小部件,它调用一个闭包来获取它的子小部件。

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好吧,我承认:这并没有多大帮助......但足以说(遵循另一个 SO 线程

The purpose of the Builder class is simply to build and return child widgets. Builder类的目的只是构建和返回子小部件。

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)通过在Scaffold 中调用Builder ,我们正在构建 Scaffold 以便能够获得它自己的上下文,并且有了这个innerContext我们最终可以调用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.')
            ));
          }
        );
      }
    )
  );
}

Use ScaffoldMessenger (Recommended)使用ScaffoldMessenger (推荐)

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

Example (Without Builder or GlobalKey )示例(没有BuilderGlobalKey

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

From Flutter version 1.23-18.1.pre you can use ScaffoldMessenger从 Flutter 版本 1.23-18.1.pre 开始,您可以使用 ScaffoldMessenger

final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

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

Somewhere inside app:应用程序内的某处:

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

A more efficient solution is to split your build function into several widgets.更有效的解决方案是将您的构建功能拆分为多个小部件。 This introduce a 'new context', from which you can obtain Scaffold这引入了一个“新上下文”,您可以从中获得 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'),
              ),
            );
          }),
    );
  }
}

I wouldn't bother using the default snackbar, because you can import a flushbar package, which enables greater customizability:我不会费心使用默认的小吃店,因为您可以导入一个flushbar 包,这可以实现更大的可定制性:

https://pub.dev/packages/flushbar https://pub.dev/packages/flushbar

For example:例如:

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

I may be late.我可能迟到了。 But this will help someone too.但这也会帮助某人。 Add a _key under the Scaffold .Scaffold下添加一个_key Then use that _key to call the openDrawer method.然后使用该_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
       
        },
      ),]

Extract your button widget which will display snackbar.提取将显示小吃栏的按钮小部件。

class UsellesslyNestedButton extends StatelessWidget {
  const UsellesslyNestedButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: (){
        showDefaultSnackbar(context);
                    },
                    color: Colors.blue,
                    child: Text('Show about'),
                  );
  }
}

here we use a builder to wrap in another widget where we need snackbar在这里,我们使用构建器将另一个小部件包装在我们需要小吃店的地方

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
           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")));

Try this code:试试这个代码:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 未处理的异常:使用不包含 Scaffold 的上下文调用 Scaffold.of() - Unhandled Exception: Scaffold.of() called with a context that does not contain a Scaffold Flutter :此错误出现在使用不包含 Scaffold 的上下文调用的 Scaffold.of() 。 当我尝试展示小吃店时 - Flutter : this error appear Scaffold.of() called with a context that does not contain a Scaffold. when i try to show a snackbar 处理手势时引发了以下断言: Scaffold.of() 使用不包含 Scaffold 的上下文调用 - The following assertion was thrown while handling a gesture: Scaffold.of() called with a context that does not contain a Scaffold 上下文的颤振脚手架“不包含脚手架” - Flutter scaffold of context giving "does not contain a Scaffold" Snackbar 和异常:scaffold.of() - Snackbar and exception: scaffold.of() Flutter Scaffold.of(context).openDrawer() 不起作用 - Flutter Scaffold.of(context).openDrawer() doesn't work 错误:没有名为“nullOk”的命名参数 Scaffold.of(context, nullOk: true) - Error: No named parameter with the name 'nullOk' Scaffold.of(context, nullOk: true) Scaffold.of会找到遥远的窗口小部件吗? - Will Scaffold.of ever find distant widget anestors? 尝试使用 Scaffold.of(context) 使用 openDrawer() 找到 2 而不是 0 时出现太多位置 arguments 错误 - Too many positional arguments error when trying to use openDrawer() using Scaffold.of(context) finding 2 instead of 0 为什么传递BuildContext表示该Context中没有Scaffold? - Why does passing BuildContext say there is no Scaffold in that Context?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM