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

如你所见,我的按钮在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);
132391 次浏览

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

使用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 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)已弃用,改用ScaffoldMessenger

从方法的文档中检查:

ScaffoldMessenger现在处理间小吃店,以便持久化 并且总是显示在当前脚手架上。缺省情况下, 根ScaffoldMessenger包含在MaterialApp中,但是你可以 为ScaffoldMessenger创建自己的控制范围 控制哪个脚手架接收你的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!'),
));
},
),
);
},
),
);
}

你可以查看详细的弃用和新方法在这里:

解决这个问题的简单方法是用下面的代码为你的脚手架创建一个键:

第一个:GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey< 代码();< / > < / p >

第二:将键分配给你的脚手架key: _scaffoldKey

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

你可以用两种方法来解决这个问题:

1)使用Builder小部件 < BR >

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

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

更新03.03.2021:颤振2.0刚刚发布…

(出于历史价值,我还在我最初的答案下面进一步说明了……)


现在…输入……

ScaffoldMessenger .

的docu我们读取

脚手架中的SnackBar API现在由Scaffold messenger处理,其中一个在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将完成一个英雄动画,平滑地移动到下一页。

ScaffoldMessenger创建了一个作用域,在这个作用域中,所有后代scaffold都注册来接收snackbar,这就是它们如何在这些转换中持续存在。当使用MaterialApp提供的根ScaffoldMessenger时,所有后代scaffold都会接收SnackBars,除非在树的更下方创建了一个新的ScaffoldMessenger作用域。通过实例化您自己的ScaffoldMessenger,您可以根据应用程序的上下文控制哪些scaffold接收snackbar,哪些不接收。


原来的答案

你正在经历的行为甚至被称为“棘手的案件”。在Flutter 文档

如何修复

你可以从这里发布的其他答案中看到,这个问题以不同的方式修复。例如,我引用的这篇文档通过使用Builder来解决这个问题

一个内部BuildContext,以便onPressed方法可以用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.')
));
}
);
}
)
);
}

现在给好奇的读者一些细节

我自己发现探索Flutter文档非常有指导意义,只需(Android Studio)将光标设置在一段代码上(Flutter类,方法等),并按ctrl+B显示该特定片段的文档。

你所面临的特定问题在BuildContext的文档中提到,可以在那里读取

每个小部件都有自己的BuildContext,它成为[…]返回的小部件的父部件。构建函数。

因此,这意味着在我们的例子中上下文 是我们的Scaffold小部件当它创建时(!)的父部件。而且,Scaffold.of的文档说它返回

该类中包含给定上下文的最接近[脚手架]实例的状态。

但是在我们的例子中,上下文并没有包含脚手架(它还没有被构建)。这就是构建器开始起作用的地方!

文档再一次为我们提供了启示。我们可以读到

一个柏拉图式的小部件,它调用一个闭包来获取它的子小部件。

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

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

现在一切都清楚了!通过在脚手架中调用构建器,我们正在构建脚手架,以便能够获得它自己的上下文,并且有了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);

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

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。你可以使用脚手架信使

final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();


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

app内部某处:

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