在不包含Scaffold的上下文中调用Scaffold.of()

时间:2018-07-12 11:32:11

标签: flutter scaffold snackbar

您可以看到我的按钮在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的键,则可以如下所示显示SnackBar,而无需使用Builder小部件包装我们的主体。返回Scaffold的小部件应该是有状态的小部件。:

 _scaffoldKey.currentState.showSnackBar(snackbar); 

15 个答案:

答案 0 :(得分:76)

发生此异常是因为您正在使用实例化context的窗口小部件的Scaffold。不是context的孩子的Scaffold

您可以通过使用其他上下文来解决此问题:

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重构)

答案 1 :(得分:27)

您可以使用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
}

答案 2 :(得分:9)

有两种方法可以解决此问题

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: showSnackbar(context),
        ),
      ),
    );
  }
}

void showSnackbar(BuildContext context) {
  final snackBar = SnackBar(content: Text('Profile saved'));
  globalKey.currentState.showSnackBar(snackBar);  
}

答案 3 :(得分:8)

Flutter documentation中,您遇到的行为甚至被称为“棘手案例”。

如何修复

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

内部BuildContext,以便onPressed方法可以与Scaffold一起引用Scaffold.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 < / strong>类,方法等),然后按ctrl + B可以显示该特定部分的文档。

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

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

因此,这意味着在我们的情况下,上下文 将成为我们脚手架小部件创建后的父级(!)。此外, Scaffold.of 的文档表示返回

该类的最接近[ Scaffold ]实例的状态,该实例封装了给定的上下文。

但是在我们的案例中,上下文尚未包含(尚未) Scaffold (尚未构建)。在这里 Builder 开始生效!

再一次,docu照亮了我们。在那里我们可以阅读

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

答案 4 :(得分:2)

我不会使用默认的快餐栏,因为您可以导入冲洗栏包,从而实现更大的可定制性:

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

答案 7 :(得分:1)

从方法文档中进行检查:

  

当在相同的构建函数中实际创建了脚手架时,   构建函数的上下文参数不能用于查找   脚手架(因为它在返回的小部件“上方”)。在这样的   情况下,可以使用以下带有Builder的技术来提供   BuildContext位于“支架”下方的新作用域:

@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: () {
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

您可以从of method文档中查看说明

答案 8 :(得分:1)

一个更有效的解决方案是将您的构建功能分成几个小部件。 这引入了一个“新上下文”,您可以从中获得脚手架

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

答案 9 :(得分:1)

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

答案 10 :(得分:1)

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

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

答案 11 :(得分:0)

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

_scaffoldKey = GlobalKey<ScaffoldState>();并使用点运算符调用您的点心栏。

_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

答案 12 :(得分:0)

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

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 :(得分:0)

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

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

答案 14 :(得分:0)

尝试以下代码:

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