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


183

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

 _scaffoldKey.currentState.showSnackBar(snackbar); 

我发现他在那里做了一个包装,以便他可以很容易地改变或控制整个应用程序的情况下很好的教程:noobieprogrammer.blogspot.com/2020/06/...
doppelgunner

Answers:


245

发生此异常是因为您正在使用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重构)


我得到这样的异常:在构建期间调用setState()或markNeedsBuild()。I / flutter(21754):无法将此Scaffold小部件标记为需要构建,因为该框架已在I / flutter(21754):构建小部件的过程中。
FigenGüngör18年

1
onPressed你传送到RaisedButton不是一个函数。将其更改为() => _displaySnackBar(context)
雷米Rousselet

不可思议:onPressed:(){_displaySnackBar(context);},谢谢=)
FigenGüngör18年

1
我认为使用.of(context)时,它应该沿小部件层次结构向上移动,直到遇到给定类型之一(在本例中为Scaffold),它在到达“小部件build(..”之前应该遇到)。这样吗
CodeGrue

@RémiRousselet,Flutter中有几种类型的上下文?就像您说的小部件的上下文,即脚手架的子代的上下文。
CopsOnRoad

121

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

这样做的好处是,您还可以将此键传递给不包含任何脚手架的其他自定义窗口小部件类。见(这里

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

感谢Lebohang Mbele
开发人员

1
当您的树由多个文件中定义的多个小部件组成时,我能否请问如何复制它?_scaffoldKey由于下划线领先,因此是私有的。但是,即使不是,它也位于HomePage类内部,因此如果不在树下某处实例化新的HomePage(似乎创建了循环引用)就无法使用。
ExactaBox

1
回答我的问题:创建一个单独的类与GlobalKey<ScaffoldState>属性,编辑控件的构造与支架设置单的关键属性,然后在子控件的树,我们可以调用像mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox

为什么效率低下?
user2233706

@ user2233706这GlobalKey的文档中提到这里。同样,该帖子可能会更加清晰。
Lebohang Mbele

43

解决这个问题的两种方法

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

16

从方法文档中进行检查:

当实际上在同一构建函数中创建了脚手架时,该构建函数的context参数不能用于找到该脚手架(因为它在返回的小部件“上方”)。在这种情况下,可以使用以下带有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!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

您可以从docs 方法中查看说明


13

解决此问题的简单方法是使用以下代码为脚手架创建最终的钥匙,例如:

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

步骤:将密钥分配给您的支架 key: _scaffoldKey

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


3

Flutter 文档中甚至将您遇到的行为称为“棘手案例” 。

怎么修

从此处发布的其他答案中可以看出,此问题已通过不同的方式解决。例如,我引用的那篇文档通过使用Builderwhich 解决了这个问题

一个内部,BuildContext以便onPressed方法可以引用Scaffoldwith Scaffold.of()

因此showSnackBarScaffold拨打电话的方式是

@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即可显示该特定部分的文档,对Flutter文档的探索很有启发性。

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

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

因此,这意味着在我们的情况当创建 Scaffold小部件(!)时它将成为其父级。此外,Scaffold.of的文档表明它返回

此类的最接近的[ Scaffold ]实例的状态,它包围给定的上下文。

但是在我们的情况下,上下文尚未包含(但尚未)一个脚手架(尚未构建)。在哪里有建设者起作用的!

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

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

嘿,等等,什么!?好的,我承认:这无济于事...但是足以(在另一个SO线程之后)说

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

1

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

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

0

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

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

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

您的方法与现有答案相比如何?是否有理由选择这种方法而不是接受的答案?
Jeremy Caney

0

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

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.