我试图通过点击 flutter 中的按钮来创建一个小吃栏,但出现异常,找不到
ScaffoldMessenger
小部件。相同的代码似乎在文档中提到的 Flutter sample 中可以正常工作。我在这里错过了什么吗?谢谢。
这是我的 main.dart 文件
import 'package:flutter/material.dart';
void main() => runApp(MyAppWidget());
class MyAppWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyAppWidget> {
final _inputKey = GlobalKey<FormState>();
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
String inputText = "";
String appendString() {
setState(() {
inputText += inputText;
});
return inputText;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
key: _inputKey,
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_inputKey.currentState.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Processing Data')));
}
},
child: Text("Enter"),
),
Text(appendString())
],
),
),
),
);
}
}
这是我遇到的异常
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No ScaffoldMessenger widget found.
MyAppWidget widgets require a ScaffoldMessenger widget ancestor.
Handler: "onTap"
#4 _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:991
#3 _MyAppState.build.<anonymous closure>
package:assignment_1/main.dart:48
#2 ScaffoldMessenger.of
package:flutter/…/material/scaffold.dart:224
#1 debugCheckHasScaffoldMessenger
package:flutter/…/material/debug.dart:153
#0 debugCheckHasScaffoldMessenger.<anonymous closure>
package:flutter/…/material/debug.dart:142
When the exception was thrown, this was the stack
Typically, the ScaffoldMessenger widget is introduced by the MaterialApp at the top of your application widget tree.
renderObject: RenderView#a5074
[root]
The ancestors of this widget were
state: _MyAppState#a2cb9
Restarted application in 691ms.
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
No ScaffoldMessenger widget found.
MyAppWidget widgets require a ScaffoldMessenger widget ancestor.
The specific widget that could not find a ScaffoldMessenger ancestor was: MyAppWidget
回答最初的问题:“我在这里缺少什么?”:
ScaffoldMessenger.of(context)
使用的 context
不在下面(即不是 MaterialApp
的子级)。 (上面找不到 ScaffoldMessenger
。)
原生 Flutter 静态方法
<SomeClassName>.of(context)
沿着 widget 树查找名为 InheritedWidget
的 <SomeClassName>
类型父级。所以 ScaffoldMessenger.of(context)
正在寻找 ScaffoldMessenger
家长。1
MaterialApp
小部件为我们提供了 ScaffoldMessenger
,因为它将其子级包装在 ScaffoldMessenger
中。以下是 MaterialApp's
“构建器”方法的摘录:
Widget _materialBuilder(BuildContext context, Widget? child) {
return ScaffoldMessenger( // <<<< <<<< hello Mr. ScaffoldMessenger
key: widget.scaffoldMessengerKey,
child: AnimatedTheme(
data: theme,
child: widget.builder != null
? Builder(
builder: (BuildContext context) {
return widget.builder!(context, child); // your kid
},
)
: child ?? const SizedBox.shrink(),
),
);
}
上面的
child
可能是您提供给 MaterialApp
的最顶层小部件。只有该小部件的构建方法及以下才能在其父祖先中找到ScaffoldMessenger
中的MaterialApp
。
原始问题的
.showSnackBar()
正在查找MyApp's
上下文/父母祖先,不是MaterialApp's
。
MyApp(context)
-> MaterialApp(gets MyApp context) + other Widget(gets MyApp context) (no ScaffoldMessenger avail!)
-> kids (gets MaterialApp context, thus ScaffoldMessenger above)
我们使用的
context
真的很容易弄错。
在原始代码片段中,我们使用此级别最接近的可见
context
,即MyApp's
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) { // <<<< this MyApp context...
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
key: _inputKey,
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_inputKey.currentState.validate()) {
ScaffoldMessenger.of(context).showSnackBar( // is this context <<<
SnackBar(content: Text('Processing Data')));
}
即使
Scaffold > Form > ElevatedButton
小部件物理上显示为“较低/下方”MaterialApp
,但它们当前位于 MyApp's build(BuildContext context)
方法内,因此当前正在使用 MyApp's
context
。
要使用
MaterialApp
的上下文,需要从位于 inside
Scaffold/Form/ElevatedButton
的 build(BuildContext context)
方法内部调用 MaterialApp
,从而获得其 context
。
避免上述陷阱的一种方法是保持
MyApp/MaterialApp
非常简单,并从另一个层次开始编码,如下例中的 HomePage
所示:
class MyApp extends StatelessWidget {
const MyApp({Key? key, required this.bindings}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(), // <<< put your stuff in HomePage's build() method
);
}
}
或者,我们可以将
home:
小部件包装在 Builder
小部件 (docs) 中,然后 Builder
中的每个子级都将使用 MaterialApp's context
,它具有第一个可用的 ScaffoldMessenger
。
class MyApp extends StatelessWidget {
const MyApp({Key? key, required this.bindings}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(builder: (BuildContext context) {
// stuff here gets MaterialApp context
}),
);
}
}
正如其他答案中提到的,我们可以使用
scaffoldMessengerKey
arg 使用 GlobalKey<ScaffoldMessengerState>
,如引入 ScaffoldMessenger
时的 迁移指南中所述。
1 这个
InheritedWidget
查找到的.of()
类型实际上是_ScaffoldMessengerScope
,其中包含ScaffoldMessengerState
字段,该字段是提供我们感兴趣的SnackBar
API方法的对象,例如.showSnackBar()
按照EngineSense的建议,创建了一个全局密钥
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
并将其添加到按钮的 onPressed 方法中
_messangerKey.currentState.showSnackBar(
SnackBar(content: Text('Processing Data')));
以下是更新后的更改供参考。
import 'package:flutter/material.dart';
void main() => runApp(MyAppWidget());
class MyAppWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyAppWidget> {
final _inputKey = GlobalKey<FormState>();
final _messangerKey = GlobalKey<ScaffoldMessengerState>();
String inputText = "";
String appendString() {
setState(() {
inputText += inputText;
});
return inputText;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _messangerKey,
home: Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
key: _inputKey,
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_inputKey.currentState.validate()) {
_messangerKey.currentState.showSnackBar(
SnackBar(content: Text('Processing Data')));
}
},
child: Text("Enter"),
),
Text(appendString())
],
),
),
),
);
}
}
scaffoldMessengerKey.currentState.showSnackBar(mySnackBar);脚手架MessengerKey.currentState.hideCurrentSnackBar(mySnackBar); scaffoldMessengerKey.currentState.removeCurrentSnackBar(mySnackBar);
您可以将 SnackBarGlobal 设为一个类并使其静态,以便您可以在整个应用程序中使用它。
这是一个小示例,您可以在不同的小部件中更改导航,并且在每个小部件中您都可以触发 SnackBarGlobal。在 DartPad
上查看您还可以查看 文档,其中解释了错误 Scaffold 小部件需要 ScaffoldMessenger 小部件祖先
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: SnackbarGlobal.key,
routes: routes,
initialRoute: 'pageOne',
);
}
}
class SnackbarGlobal {
static GlobalKey<ScaffoldMessengerState> key =
GlobalKey<ScaffoldMessengerState>();
static void show(String message) {
key.currentState!
..hideCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(message)));
}
}
class MyPage extends StatelessWidget {
final String title;
final String route;
MyPage(this.title, this.route);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
child: Text('Go to $route'),
onPressed: () {
Navigator.pushNamed(context, route);
},
),
TextButton(
child: Text('Show SnackBar in $title'),
onPressed: () {
SnackbarGlobal.show('This is a SnackBar in $title');
},
),
],
),
),
);
}
}
final routes = <String, WidgetBuilder>{
'pageOne': (BuildContext context) => MyPage('My Page One', 'pageTwo'),
'pageTwo': (BuildContext context) => MyPage('My Page Two', 'pageThree'),
'pageThree': (BuildContext context) => MyPage('My Page Three', 'pageOne'),
};
我遇到了同样的问题,并发现了奇怪的行为。
下面的代码是从显示小吃栏复制的,效果很好
import 'package:flutter/material.dart';
void main() => runApp(const SnackBarDemo());
class SnackBarDemo extends StatelessWidget {
const SnackBarDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SnackBar Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('SnackBar Demo'),
),
body: const SnackBarPage(),
),
);
}
}
class SnackBarPage extends StatelessWidget {
const SnackBarPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: const Text('Show SnackBar'),
),
);
}
}
但是,当我删除
SnackBarPage
类,并将 build()
方法中的代码直接复制到 SnackBarDemo
类时,如下所示。
import 'package:flutter/material.dart';
void main() => runApp(const SnackBarDemo());
class SnackBarDemo extends StatelessWidget {
const SnackBarDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SnackBar Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('SnackBar Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: const Text('Show SnackBar'),
),
)),
);
}
}
然后它将不再不再工作,并提高
No ScaffoldMessenger widget found. SnackBarDemo widgets require a ScaffoldMessenger widget ancestor.
,似乎ScaffoldMessenger
仅在包裹在某些Widget
类中时才起作用。我不知道为什么。
GitHub 上的 Flutter 图库显示该功能将获得升级,并且当他们再次将 flutter master 分支与更新的分支合并时,该功能将会更新。目前,您可以仅使用 Scaffold 或在关键变量中为其编写手动代码后将其实现为关键。
对我有用的是为 Scaffold() 提供一个单独的 StatefulWidget(如下 MyScaffold)。它和我一起工作。
import 'package:flutter/material.dart';
void main() => runApp(MyAppWidget());
class MyAppWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyAppWidget> {
@override
Widget build(BuildContext context) {
return MaterialApp(home: MyScaffold());
}
}
class MyScaffold extends StatefulWidget {
const MyScaffold({Key? key}) : super(key: key);
@override
_MyScaffoldState createState() => _MyScaffoldState();
}
class _MyScaffoldState extends State<MyScaffold> {
String inputText = "";
String appendString() {
setState(() {
inputText += inputText;
});
return inputText;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Assignment"),
),
body: Form(
child: Column(
children: [
TextFormField(
validator: (inputString) {
inputText = inputString!;
if (inputString.length < 5) {
return 'Please enter a longer string';
}
return null;
},
),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
},
child: Text("Enter"),
),
Text(appendString())
],
),
),
);
}
}
这是我的第一个答案,所以我对格式化不太了解。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage()
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My App"),
actions: [
IconButton(
onPressed: (){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Snackbar')
)
);
},
icon: Icon(Icons.favorite)
)
],
),
);
} }
只需在构建器小部件下使用 onpress 并将构建器上下文传递给scaffoldmessenger。
使用这个:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: _content ,duration: Duration(milliseconds: 200),));