Flutter:找不到 ScaffoldMessenger 小部件

Pra*_*aik 18 flutter flutter-layout

我试图通过点击颤动中的按钮来创建一个小吃栏,但出现异常,找不到ScaffoldMessenger小部件。相同的代码似乎在文档中提到的 Flutter示例中可以正常工作。我在这里错过了什么吗?谢谢。

\n

这是我的 main.dart 文件

\n
import \'package:flutter/material.dart\';\n\nvoid main() => runApp(MyAppWidget());\n\nclass MyAppWidget extends StatefulWidget {\n  @override\n  State<StatefulWidget> createState() {\n    return _MyAppState();\n  }\n}\n\nclass _MyAppState extends State<MyAppWidget> {\n  final _inputKey = GlobalKey<FormState>();\n  final _messangerKey = GlobalKey<ScaffoldMessengerState>();\n  String inputText = "";\n\n  String appendString() {\n    setState(() {\n      inputText += inputText;\n    });\n    return inputText;\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      scaffoldMessengerKey: _messangerKey,\n      home: Scaffold(\n        appBar: AppBar(\n          title: Text("Assignment"),\n        ),\n        body: Form(\n          key: _inputKey,\n          child: Column(\n            children: [\n              TextFormField(\n                validator: (inputString) {\n                  inputText = inputString;\n                  if (inputString.length < 5) {\n                    return \'Please enter a longer string\';\n                  }\n                  return null;\n                },\n              ),\n              ElevatedButton(\n                onPressed: () {\n                  if (_inputKey.currentState.validate()) {\n                    ScaffoldMessenger.of(context).showSnackBar(\n                        SnackBar(content: Text(\'Processing Data\')));\n                  }\n                },\n                child: Text("Enter"),\n              ),\n              Text(appendString())\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是我遇到的异常

\n
\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90 Exception caught by gesture \xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following assertion was thrown while handling a gesture:\nNo ScaffoldMessenger widget found.\n\nMyAppWidget widgets require a ScaffoldMessenger widget ancestor.\n\nHandler: "onTap"\n\n#4      _InkResponseState._handleTap\npackage:flutter/\xe2\x80\xa6/material/ink_well.dart:991\n#3      _MyAppState.build.<anonymous closure>\npackage:assignment_1/main.dart:48\n#2      ScaffoldMessenger.of\npackage:flutter/\xe2\x80\xa6/material/scaffold.dart:224\n#1      debugCheckHasScaffoldMessenger\npackage:flutter/\xe2\x80\xa6/material/debug.dart:153\n#0      debugCheckHasScaffoldMessenger.<anonymous closure>\npackage:flutter/\xe2\x80\xa6/material/debug.dart:142\nWhen the exception was thrown, this was the stack\n\nTypically, the ScaffoldMessenger widget is introduced by the MaterialApp at the top of your application widget tree.\n\n        renderObject: RenderView#a5074\n    [root]\nThe ancestors of this widget were\n    state: _MyAppState#a2cb9\nRestarted application in 691ms.\n\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90 Exception caught by gesture \xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following assertion was thrown while handling a gesture:\nNo ScaffoldMessenger widget found.\n\nMyAppWidget widgets require a ScaffoldMessenger widget ancestor.\nThe specific widget that could not find a ScaffoldMessenger ancestor was: MyAppWidget\n\n
Run Code Online (Sandbox Code Playgroud)\n

Bak*_*ker 19

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

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

ScaffoldMessenger.of(context)正在使用contextnot Below(即不是其子项)MaterialApp (上面没有ScaffoldMessenger找到。)

本机 Flutter 静态方法<SomeClassName>.of(context)会沿着 widget 树查找InheritedWidget名为 的类型父级<SomeClassName>ScaffoldMessenger.of(context)寻找父母也是如此ScaffoldMessenger1

MaterialAppwidget 为我们提供了 aScaffoldMessenger因为它将它的子级包装在 a 中ScaffoldMessengerMaterialApp'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(),
      ),
    );
  }
Run Code Online (Sandbox Code Playgroud)

上面的内容child可能是您提供给的最顶层的小部件MaterialApp。只有该小部件的构建方法及其以下版本才能在其父祖先中找到ScaffoldMessengerfrom 。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)
Run Code Online (Sandbox Code Playgroud)

原始问题的代码

我们使用的确实很容易出错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')));
                  }
Run Code Online (Sandbox Code Playgroud)

尽管Scaffold > Form > ElevatedButton小部件在物理上显示为“较低/下方” MaterialApp,但它们当前位于MyApp's build(BuildContext context)方法内,因此当前正在使用MyApp's context.

要使用 的上下文MaterialApp,需要从内部方法内部Scaffold/Form/ElevatedButton调用,从而获取其.build(BuildContext context) MaterialAppcontext

避免上述陷阱的一种方法是保持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
    );
  }

}
Run Code Online (Sandbox Code Playgroud)

或者,我们可以将home:小部件包装在Builder小部件(文档)中,然后其中的每个子部件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
      }),
    );
  }

}
Run Code Online (Sandbox Code Playgroud)

正如其他答案中提到的,我们可以使用scaffoldMessengerKeyarg,如迁移指南GlobalKey<ScaffoldMessengerState>中介绍的那样。ScaffoldMessenger


1 这个查找到的类型其实是,里面包含了字段,就是提供我们感兴趣的API方法的对象,比如InheritedWidget.of()_ScaffoldMessengerScopeScaffoldMessengerStateSnackBar.showSnackBar()


Pra*_*aik 15

按照EngineSense的建议,创建了一个全局密钥

final _messangerKey = GlobalKey<ScaffoldMessengerState>();
Run Code Online (Sandbox Code Playgroud)

并将其添加到按钮的 onPressed 方法中

_messangerKey.currentState.showSnackBar(
                        SnackBar(content: Text('Processing Data')));
Run Code Online (Sandbox Code Playgroud)

以下是更新后的更改,供参考。

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

Run Code Online (Sandbox Code Playgroud)


Tot*_*Tot 3

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