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

Flutter 如何从根祖先访问Flatter中最新的脚手架?,flutter,Flutter,我的应用程序在树的根目录下包含一个StatefulWidget,它通过回调将状态更改为InheritedWidget。在其中一个回调中,会创建Firebase Firestore中文档的侦听器,以侦听更改,然后显示一个SnackBar,通知用户更改 现在的问题是如何从树根的父级访问当前活动的Scaffold。根目录和当前活动的Scaffold之间可能有多个其他Scaffold,具体取决于推送到导航器上的路径数。但是要显示一个SnackBar必须使用最近的一个。我当前的解决方案可以在下面的代码示例

我的应用程序在树的根目录下包含一个
StatefulWidget
,它通过回调将状态更改为
InheritedWidget
。在其中一个回调中,会创建Firebase Firestore中文档的侦听器,以侦听更改,然后显示一个
SnackBar
,通知用户更改


现在的问题是如何从树根的父级访问当前活动的
Scaffold
。根目录和当前活动的
Scaffold
之间可能有多个其他
Scaffold
,具体取决于推送到
导航器上的路径数。但是要显示一个
SnackBar
必须使用最近的一个。

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

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

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);
    });
  }
}
类MyApp扩展StatefulWidget{
最终消防仓库消防仓库;
const MyApp(this.firestore);
@凌驾
状态createState(){
返回新的MyAppState();
}
}
类MyAppState扩展了状态{
void addDocumentListener(DocumentReference ref){
参考快照。侦听((快照){
_scaffoldKeys.last.currentState.showSnackBar(新SnackBar(内容:新文本(“测试”));
});
}
var_scaffoldKeys=新队列();
空推脚手架键(全局键){
_脚手架钥匙。添加最后一个(钥匙);
}
GlobalKey popScaffoldKey(){
返回_scaffoldKeys.removeLast();
}
@凌驾
小部件构建(构建上下文){
返回新的MyInheritedWidget(addDocumentListener、pushScaffoldKey、popScaffoldKey、new MaterialApp(主页:new HomePage());
}
}
类型def VoidDocumentReferenceCallback(DocumentReference ref);
typedef VoidGlobalKeyScaffoldStateCallback(GlobalKey);
typedef GlobalKey GlobalKeyScaffoldStateCallback();
类MyInheritedWidget扩展了InheritedWidget{
最终作废文档引用回调添加文档监听器;
最终作废GlobalKeyScaffoldStateCallback pushScaffoldKey;
最终GlobalKeyScaffoldStateCallback popScaffoldKey;
const MyInheritedWidget(this.addDocumentListener、this.pushScaffoldKey、this.popScaffoldKey、Widget子项):超级(子项:子项);
@凌驾
bool updateShouldNotify(继承的小部件oldWidget){
返回false;
}
的静态MyInheritedWidget(BuildContext上下文){
返回context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
}
类主页扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回新的CustomScaffold(
appBar:新的appBar(
标题:新文本(“主页”),
行动:[
新图标按钮(图标:const图标(Icons.settings)),ON按下:(){
Navigator.of(context.push(newmaterialpageroute)(生成器:(context){
//使用自定义脚手架转到另一页
}));
})
],
),
正文:新中心(
子项:新升起按钮(按下时:(){
MyInheritedWidget.of(context).addDocumentListener(ref);
},子项:新文本(“添加侦听器”),
),
);
}
}
类CustomScaffold扩展StatefulWidget{
最终AppBar AppBar;
最终窗口小部件主体;
const CustomScaffold({this.appBar,this.body});
@凌驾
状态createState(){
返回新的CustomScaffoldState();
}
}
类CustomScaffoldState扩展了状态{
final _key=new GlobalKey();
bool _keycinitialized=false;
@凌驾
didChangeDependencies(){
如果(!\u键已初始化){
MyInheritedWidget.of(context).pushScaffoldKey(_键);
_keyInitialized=true;
}
super.didChangeDependencies();
}
@凌驾
小部件构建(构建上下文){
var scaffold=新脚手架(
键:_键,
appBar:widget.appBar,
body:widget.body,
);
返回新的WillPopScope(子项:scaffold,onWillPop:(){
MyInheritedWidget.of(context).popScaffoldKey();
返回新的未来值(true);
});
}
}
TLDR:

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

详细信息

我的应用程序略有不同,但我相信我的解决方案仍然适用

在我的应用程序中,我有几个在不同页面上运行的提供者,但可能都需要创建一个snackbar。因此,提供者需要知道哪个脚手架是活动的

我发现并使用了
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);
  }
}
要从上述调用中显示快餐店的每个页面必须在其构建中包含以下内容:

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()
        ),
      }
  });
}
小部件构建(构建上下文){
退货消费者(
生成器:(uu,bleDeviceArc003Provider,uuu){
bleDeviceArc003Provider.setSnackBarContext(上下文,_scaffoldKey);
返回式示波器(
onWillPop:_onPop,
孩子:脚手架(
钥匙:_scaffoldKey,
appBar:appBar(),
正文:ListView()
),
}
});
}

如果您添加一些示例,我认为这会很有用code@Azsgy根据你的要求,我添加了一些示例代码。@RémiRousselet我添加了一个解释,说明了为什么这篇文章不是重复的。请删除关于重复的标志。另外,如果这篇文章需要进一步澄清,请告诉我。我仍然不明白这是怎么回事
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()
        ),
      }
  });
}