繁体   English   中英

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

[英]Scaffold.of() called with a context that does not contain a 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<ScaffoldState> ,我们可以如下显示 SnackBar,而不需要将我们的主体包装在Builder小部件中。 不过,返回Scaffold的小部件应该是有状态的小部件。

 _scaffoldKey.currentState.showSnackBar(snackbar); 

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

您可以使用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
  }
}

您可以通过两种方式解决此问题:

1) 使用 Builder 小部件

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) 使用全局键

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

更新 - 2021

不推荐使用Scaffold.of(context)以支持ScaffoldMessenger

从方法文档中检查这一点:

ScaffoldMessenger现在处理SnackBars以便跨路由持久化并始终显示在当前 Scaffold 上。 默认情况下, MaterialApp 中包含根ScaffoldMessenger ,但您可以为ScaffoldMessenger创建自己的受控范围,以进一步控制哪些 Scaffolds 接收您的 SnackBar。

@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<ScaffoldState> ();

Scecond:将密钥分配给您的 Scaffold key: _scaffoldKey

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

2021 年 3 月 3 日更新:Flutter 2.0 刚刚发布...

(就历史价值而言,我还进一步低于我的原始答案......)


现在...输入...

脚手架信使

从我们阅读的文档中

Scaffold 中的 SnackBar API 现在由 ScaffoldMessenger 处理,其中之一在 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 会完成一个 Hero 动画,平滑地移动到下一页。

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


原答案

您所遇到的这种行为甚至在Flutter文档中被称为“棘手案例”。

怎么修

正如您可以从此处发布的其他答案中看到的那样,该问题已以不同方式解决。 例如,一条文档编制的我用指解决问题的Builder ,其产生

BuildContext使得onPressed方法可以参照ScaffoldScaffold.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.')
            ));
          }
        );
      }
    )
  );
}

现在为好奇的读者提供一些细节

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

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

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

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

来自包含给定上下文的此类的最近 [ Scaffold ] 实例的状态。

但是在我们的例子中,上下文没有(还)包含一个脚手架(它还没有被构建)。 Builder在那里开始行动了!

文档再一次照亮了我们。 在那里我们可以阅读

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

使用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'),
  ),
)

从 Flutter 版本 1.23-18.1.pre 开始,您可以使用 ScaffoldMessenger

final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

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

应用程序内的某处:

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

我不会费心使用默认的小吃店,因为您可以导入一个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);

我可能迟到了。 但这也会帮助某人。 Scaffold下添加一个_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
       
        },
      ),]

提取将显示小吃栏的按钮小部件。

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

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

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
           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")));

试试这个代码:

Singleton.showInSnackBar(
    Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM