如何从根祖先访问 Flutter 中最新的脚手架?

Posted

技术标签:

【中文标题】如何从根祖先访问 Flutter 中最新的脚手架?【英文标题】:How to access the most recent scaffold in Flutter from the root ancestor? 【发布时间】:2018-10-02 17:02:09 【问题描述】:

我的应用由位于树根的StatefulWidget 组成,它传递其状态以及将状态更改为InheritedWidget 的回调。在其中一个回调中,创建了 Firebase 的 Firestore 内文档的侦听器以侦听更改,然后显示 SnackBar 通知用户更改。

现在的问题是如何从树根的父级访问当前活动的Scaffold。在根和当前活动的Scaffold 之间可能有多个其他Scaffolds,这取决于有多少路由被推送到Navigator。但要显示SnackBar,必须使用最新的。

【问题讨论】:

我认为如果您添加一些示例代码会很有用 @Azsgy 按照您的要求,我添加了一些示例代码。 @RémiRousselet 我添加了一个解释,描述了为什么帖子不是重复的。请删除有关重复的标志。如果帖子需要进一步澄清,请告诉我。 我仍然不明白这与***.com/questions/45948168/… 提供的示例有何不同。答案还是一样的:Scaffold.of(context).showSnackbar(snackbar)。唯一的区别是您需要将context 作为addDocumentListener 的参数传递。 @RémiRousselet 我希望最新的编辑能够阐明为什么将当前 Scaffold 的上下文传递给 addDocumentListener 是不够的 【参考方案1】:

我当前的解决方案可以在以下代码示例中找到。它有效,但我对更好的想法持开放态度。我目前不喜欢这个解决方案的是我需要调用回调来从didChangeDependencies 中推送当前的GlobalKey,因此引入一个成员变量以确保在Widget 的生命周期中只调用一次,因为无法从initState 访问InheritedWidget

除了引入成员变量之外,还有其他选择吗?

class MyApp extends StatefulWidget 
  final Firestore firestore;

  const MyApp(this.firestore);

  @override
  State<StatefulWidget> createState() 
    return new MyAppState();
  


class MyAppState extends State<MyApp> 
  void addDocumentListener(DocumentReference ref) 
    ref.snapshots.listen((snapshot) 
      _scaffoldKeys.last.currentState.showSnackBar(new SnackBar(content: new Text("Test")));
    );
  

  var _scaffoldKeys = new Queue<GlobalKey<ScaffoldState>>();
  void pushScaffoldKey(GlobalKey<ScaffoldState> key) 
    _scaffoldKeys.addLast(key);
  
  GlobalKey<ScaffoldState> popScaffoldKey() 
    return _scaffoldKeys.removeLast();
  

  @override
  Widget build(BuildContext context) 
    return new MyInheritedWidget(addDocumentListener, pushScaffoldKey, popScaffoldKey, new MaterialApp(home: new HomePage()));
  


typedef VoidDocumentReferenceCallback(DocumentReference ref);
typedef VoidGlobalKeyScaffoldStateCallback(GlobalKey<ScaffoldState> key);
typedef GlobalKey<ScaffoldState> GlobalKeyScaffoldStateCallback();

class MyInheritedWidget extends InheritedWidget 
  final VoidDocumentReferenceCallback addDocumentListener;
  final VoidGlobalKeyScaffoldStateCallback pushScaffoldKey;
  final GlobalKeyScaffoldStateCallback popScaffoldKey;

  const MyInheritedWidget(this.addDocumentListener, this.pushScaffoldKey, this.popScaffoldKey, Widget child) : super(child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) 
    return false;
  

  static MyInheritedWidget of(BuildContext context) 
    return context.inheritFromWidgetOfExactType(MyInheritedWidget);
  


class HomePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new CustomScaffold(
      appBar: new AppBar(
        title: new Text("Home Page"),
        actions: <Widget>[
          new IconButton(icon: const Icon(Icons.settings), onPressed: () 
            Navigator.of(context).push(new MaterialPageRoute(builder: (context) 
              // go to another page with a CustomScaffold
            ));
          )
        ],
      ),
      body: new Center(
        child: new RaisedButton(onPressed: () 
          MyInheritedWidget.of(context).addDocumentListener(ref);
        , child: new Text("add listener")),
      ),
    );
  


class CustomScaffold extends StatefulWidget 
  final AppBar appBar;
  final Widget body;

  const CustomScaffold(this.appBar, this.body);

  @override
  State<StatefulWidget> createState() 
    return new CustomScaffoldState();
  


class CustomScaffoldState extends State<CustomScaffold> 
  final _key = new GlobalKey<ScaffoldState>();

  bool _keyInitialized = false;

  @override
  didChangeDependencies() 
    if (!_keyInitialized) 
      MyInheritedWidget.of(context).pushScaffoldKey(_key);
      _keyInitialized = true;
    
    super.didChangeDependencies();
  

  @override
  Widget build(BuildContext context) 
    var scaffold = new Scaffold(
      key: _key,
      appBar: widget.appBar,
      body: widget.body,
    );

    return new WillPopScope(child: scaffold, onWillPop: () 
      MyInheritedWidget.of(context).popScaffoldKey();
      return new Future.value(true);
    );
  

【讨论】:

【参考方案2】:

TLDR:

使用ModalRoute.of(context).isCurrent 确定当前上下文是否可见,并在小部件的构建方法中传递scaffolkey。

详情

我的申请略有不同,但我相信我的解决方案仍然适用。

在我的应用程序中,我有几个跨不同页面运行的提供程序,但可能都需要提高一个快餐栏。因此,提供者需要知道哪个脚手架处于活动状态。

我发现并使用了ModalRoute.of(context).isCurrent,它可以让您确定当前上下文是否可见。

我创建了以下 mixin:

import 'package:flutter/material.dart';

mixin SnackBarHandler 
  GlobalKey<ScaffoldState> _scaffoldKey;

  /// Sets the scaffold key if the caller is active.
  void setSnackBarContext(
      BuildContext context, GlobalKey<ScaffoldState> scaffoldKey) 
    if (ModalRoute.of(context).isCurrent) 
      _scaffoldKey = scaffoldKey;
    
  

  /// Convenience wrapper to show snack bar.
  void showSnackbar(SnackBar snackBar) 
    _scaffoldKey?.currentState?.showSnackBar(snackBar);
  

添加到您需要显示小吃店的任何类中,如下所示:

class BleDeviceProvider extends ChangeNotifier with SnackBarHandler
    showSnackbar(SnackBar());

您想在上述调用中显示小吃店的每个页面都必须在其构建中包含以下内容:

Widget build(BuildContext context) 
    return Consumer<BleDeviceArc003Provider>(
        builder: (_, bleDeviceArc003Provider, __) 
      bleDeviceArc003Provider.setSnackBarContext(context, _scaffoldKey);
      return WillPopScope(
        onWillPop: _onPop,
        child: Scaffold(
            key: _scaffoldKey,
            appBar: AppBar(),
            body: ListView()
        ),
      
  );

【讨论】:

以上是关于如何从根祖先访问 Flutter 中最新的脚手架?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter数据传输

Flutter数据传输

Flutter数据传输

Flutter:如何从 appbar 动作中显示快餐栏

防止小部件填充Flutter中扩展的祖先

在 Flutter 中,如何一次设置 *all* 脚手架背景颜色?