Flutter:找不到 ScaffoldMessenger 小部件

问题描述 投票:0回答:10

我试图通过点击 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

flutter flutter-layout
10个回答
19
投票

问题/为什么会发生这种情况

回答最初的问题:“我在这里缺少什么?”:

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


15
投票

按照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())
            ],
          ),
        ),
      ),
    );
  }
}


3
投票

scaffoldMessengerKey.currentState.showSnackBar(mySnackBar);脚手架MessengerKey.currentState.hideCurrentSnackBar(mySnackBar); scaffoldMessengerKey.currentState.removeCurrentSnackBar(mySnackBar);


3
投票

您可以将 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'),
};

1
投票

我遇到了同样的问题,并发现了奇怪的行为。

下面的代码是从显示小吃栏复制的,效果很好

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
类中时才起作用。我不知道为什么。


0
投票

GitHub 上的 Flutter 图库显示该功能将获得升级,并且当他们再次将 flutter master 分支与更新的分支合并时,该功能将会更新。目前,您可以仅使用 Scaffold 或在关键变量中为其编写手动代码后将其实现为关键。


0
投票

对我有用的是为 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())
          ],
        ),
      ),
    );
  }
}

这是我的第一个答案,所以我对格式化不太了解。


0
投票

解决方案是将脚手架放在单独的小部件中,并将小吃栏放在脚手架内,如下所示

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

} }


0
投票

只需在构建器小部件下使用 onpress 并将构建器上下文传递给scaffoldmessenger。


-1
投票

使用这个:

ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: _content ,duration: Duration(milliseconds: 200),));
© www.soinside.com 2019 - 2024. All rights reserved.