【问题标题】:使用不包含 Scaffold 的上下文调用 Scaffold.of()
【发布时间】:2018-12-20 14:55:05
【问题描述】:

如您所见,我的按钮位于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 小部件中。不过,返回 Scaffold 的小部件应该是有状态的小部件。

 _scaffoldKey.currentState.showSnackBar(snackbar); 

【问题讨论】:

标签: flutter dart snackbar scaffold


【解决方案1】:

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

【讨论】:

  • 我认为使用 .of(context) 它应该向上移动小部件层次结构,直到遇到给定类型之一,在这种情况下,脚手架,它应该在到达“小部件构建( ..”。这样不行吗?
  • @RémiRousselet,Flutter 中有多少种上下文?就像你说的小部件上下文,Scaffold 的子上下文。
  • @CodeGrue 它确实以这种方式工作,但是传递给Scaffold.of()context 在层次结构中“高于”Scaffold,因为它是传递给HomePage.build() 的上下文
  • 将您的小部件拆分为多个。
  • 对于更新的方法,@Deczaloth 的see this answer。 “The new approach 调用 ScaffoldMessenger 以显示 SnackBar。在这种情况下,不再需要 Builder 提供具有位于 Scaffold 下的 BuildContext 的新范围。”
【解决方案2】:

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

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

【讨论】:

  • 当你的树由多个文件中定义的多个小部件组成时,我能问一下如何复制这个吗?由于前导下划线,_scaffoldKey 是私有的。但即使不是,它也在 HomePage 类中,因此如果不在树下的某处实例化一个新的 HomePage 就无法使用,似乎创建了一个循环引用。
  • 回答我自己的问题:创建一个带有GlobalKey&lt;ScaffoldState&gt;属性的单例类,使用脚手架编辑Widget的构造函数以设置单例的key属性,然后在子小部件的树中我们可以调用类似mySingleton.instance.key.currentState.showSnackBar() ...
  • 为什么效率低?
  • @user2233706 这在 GlobalKey here 的文档中有所提及。 this post 也可能会带来更多启示。
  • _scaffoldKey.currentState.showSnackBar(snackBar); 已折旧。
【解决方案3】:

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

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

【讨论】:

  • 接近的都已经折旧了。
  • @Pankaj 你碰巧有关于这两个被弃用的来源吗? Builder 小部件类是最接受的答案,第二个最热门的答案使用GlobalKey(已注意到 can 是“昂贵的”以及反应性反模式)。尽管这个答案对于前两个答案来说似乎是多余的,但我似乎找不到任何被弃用的参考。感谢您提供任何见解。
  • 对于第一个解决方案,我知道Scaffold.of(context).showSnackBar(mySnackBar); has been replaced by ScaffoldMessenger.of(context).showSnackBar(mySnackBar);。这就是您所指的解决方案 1 被弃用的意思吗?编辑:哦!这是为什么? “...在这种情况下,不再需要 Builder 提供一个新的范围,它具有位于 Scaffold '下'的 BuildContext。”
  • 没关系。我在下面通过@Deczaloth 找到了the extended answer。很高兴我收到了你的评论。
【解决方案4】:

更新 - 2021

Scaffold.of(context)ScaffoldMessenger 取代。

从方法的文档中检查:

ScaffoldMessenger 现在处理 SnackBars 以便在 路线并始终显示在当前脚手架上。默认情况下,一个 ScaffoldMessenger 包含在 MaterialApp 中,但您可以 为 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!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

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

【讨论】:

    【解决方案5】:

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

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

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

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

    【讨论】:

    【解决方案6】:

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

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


    现在...输入...

    ScaffoldMessenger

    the docu我们读到

    Scaffold 中的 SnackBar API 现在由 ScaffoldMessenger 处理,其中一个默认情况下在 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'),
      ),
    );
    
    

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

    在过渡期间呈现 SnackBar 时,SnackBar 将完成一个 Hero 动画,平滑地移动到下一页。

    ScaffoldMessenger 创建一个范围,所有后代 Scaffold 都在其中注册以接收 SnackBar,这就是它们在这些转换中持续存在的方式。使用 MaterialApp 提供的根 ScaffoldMessenger 时,所有后代 Scaffold 都会收到 SnackBar,除非在树的更下方创建新的 ScaffoldMessenger 范围。通过实例化您自己的 ScaffoldMessenger,您可以控制哪些 Scaffold 接收 SnackBar,哪些不基于您的应用程序的上下文。


    原始答案

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

    如何修复

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

    一个内部的BuildContext 以便onPressed 方法可以引用ScaffoldScaffold.of()

    因此,从 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.')
                ));
              }
            );
          }
        )
      );
    }
    

    现在为好奇的读者提供一些细节

    我自己发现通过简单地 (Android Studio) 将光标设置在一段代码 (Flutter 类、方法等)并按 ctrl+B 以显示该特定部分的文档。

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

    每个小部件都有自己的 BuildContext,它成为由 [...].build 函数返回的小部件的父级。

    因此,这意味着在我们的例子中,context将是我们的 Scaffold 小部件在创建时(!)的父级。此外,Scaffold.of 的文档说它返回

    包含给定上下文的此类最近的 [Scaffold] 实例的状态。

    但在我们的例子中,context 没有(还)包含 Scaffold(它还没有被构建)。 Builder 开始行动了!

    文档再次照亮了我们。在那里我们可以阅读

    [Builder 类,简单的说] 一个柏拉图式的小部件,它调用一个闭包来获取它的子小部件。

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

    Builder 类的目的只是构建和返回子小部件。

    现在一切都清楚了!通过在 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.')
                ));
              }
            );
          }
        )
      );
    }
    

    【讨论】:

      【解决方案7】:

      从 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(...));
      

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • @Pankaj,我在更新flutter后也遇到了错误,但是在为我的vscode执行flutter clean、flutter run、重新安装插件之后,它又开始工作了,目前我处于Beta通道中
        【解决方案9】:

        更有效的解决方案是将构建功能拆分为多个小部件。 这引入了一个“新上下文”,您可以从中获得 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'),
                      ),
                    );
                  }),
            );
          }
        }
        

        【讨论】:

          【解决方案10】:

          我不会费心使用默认的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);
          

          【讨论】:

            【解决方案11】:

            我可能会迟到。但这也会帮助某人。 在 Scaffold 下添加 _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
                   
                    },
                  ),]
            

            【讨论】:

              【解决方案12】:

              提取将显示小吃栏的按钮小部件。

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

              【讨论】:

              • 您的方法与现有答案相比如何?是否有理由选择这种方法而不是接受的答案?
              【解决方案13】:

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

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

              【讨论】:

                【解决方案14】:
                           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)),
                                  ),
                                ),
                              ),
                            )
                

                【讨论】:

                  【解决方案15】:

                  也许这段代码有解决办法。

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

                  【讨论】:

                    【解决方案16】:

                    试试这个代码:

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

                    【讨论】:

                      猜你喜欢
                      • 2020-12-01
                      • 2021-04-15
                      • 2021-02-14
                      • 2021-01-28
                      • 2020-05-11
                      • 2020-11-22
                      • 2021-12-09
                      • 2020-10-25
                      • 2020-08-20
                      相关资源
                      最近更新 更多