发表日期:2018-08 文章编辑:小灯 浏览次数:2487
我在使用flutter里的对话框控件的时候遇到了一个奇怪的错误:
Another exception was thrown: Navigator operation requested with a context that does not include a Navigator
研究了一下才知道,flutter里的dialog不是随便就能用的。
原代码如下:
import 'package:flutter/material.dart';main() { runApp(new MyApp()); }class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Test', home: new Scaffold( appBar: new AppBar(title: new Text('Test')), body: _buildCenterButton(context), ), ); } } Widget _buildCenterButton(BuildContext context) { return new Container( alignment: Alignment.center, child: new Container( child: _buildButton(context), )); }Widget _buildButton(BuildContext context) { return new RaisedButton( padding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //padding child: new Text( 'show dialog', style: new TextStyle( fontSize: 18.0, //textsize color: Colors.white, // textcolor ), ), color: Theme.of(context).accentColor, elevation: 4.0, //shadow splashColor: Colors.blueGrey, onPressed: () { showAlertDialog(context); }); } void showAlertDialog(BuildContext context) { showDialog( context: context, builder: (_) => new AlertDialog( title: new Text("Dialog Title"), content: new Text("This is my content"), actions:<Widget>[ new FlatButton(child:new Text("CANCEL"), onPressed: (){ Navigator.of(context).pop();},), new FlatButton(child:new Text("OK"), onPressed: (){ Navigator.of(context).pop();},) ])); }
点击按钮的时候没有任何反应,控制台的报错是:
Another exception was thrown: Navigator operation requested with a context that does not include a Navigator。大致意思是,context里没有Navigator对象,却做了Navigator相关的操作。有点莫名其妙。
看showDialog方法的源码:
Future<T> showDialog<T>({ @required BuildContext context, bool barrierDismissible: true, @Deprecated( 'Instead of using the "child" argument, return the child from a closure ' 'provided to the "builder" argument. This will ensure that the BuildContext ' 'is appropriate for widgets built in the dialog.' ) Widget child, WidgetBuilder builder, }) { assert(child == null || builder == null); return Navigator.of(context, rootNavigator: true/*注意这里*/).push(new _DialogRoute<T>( child: child ?? new Builder(builder: builder), theme: Theme.of(context, shadowThemeOnly: true), barrierDismissible: barrierDismissible, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, )); }
Navigator.of 的源码:
static NavigatorState of( BuildContext context, { bool rootNavigator: false, bool nullOk: false, }) { final NavigatorState navigator = rootNavigator ? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>()) : context.ancestorStateOfType(const TypeMatcher<NavigatorState>()); assert(() { if (navigator == null && !nullOk) { throw new FlutterError( 'Navigator operation requested with a context that does not include a Navigator.\n' 'The context used to push or pop routes from the Navigator must be that of a ' 'widget that is a descendant of a Navigator widget.' ); } return true; }()); return navigator; }
找到了一模一样的错误信息字符串!看来就是因为Navigator.of(context)抛出了一个FlutterError。
之所以出现这个错误,是因为满足了if (navigator == null && !nullOk) 的条件, 也就是说:
context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>()) 是null。
Navigator.of函数有3个参数,第一个是BuildContext,第二个是rootNavigator,默认为false,可不传,第三个是nullOk,默认为false,可不传。rootNavigator的值决定了是调用ancestorStateOfType还是rootAncestorStateOfType,nullOk的值决定了如果最终结果为null值时该抛出异常还是直接返回一个null。
我们做个测试,传入不同的rootNavigator和nullOk的值,看有什么结果:
void showAlertDialog(BuildContext context) { try{debugPrint("Navigator.of(context, rootNavigator=true, nullOk=false)="+ (Navigator.of(context, rootNavigator: true, nullOk: false)).toString()); }catch(e){ debugPrint("error1 " +e.toString()); } try{ debugPrint("Navigator.of(context, rootNavigator=false, nullOk=false)="+(Navigator.of(context, rootNavigator: false, nullOk: false)).toString()); }catch(e){ debugPrint("error2 " +e.toString()); } try{ debugPrint("Navigator.of(context, rootNavigator=false, nullOk=true)="+(Navigator.of(context, rootNavigator: false, nullOk: true)).toString()); }catch(e){ debugPrint("error3 " +e.toString()); } //先注释掉showDialog部分的代码 //showDialog( //context: context, //builder: (_) => new AlertDialog( //title: new Text("Dialog Title"), //content: new Text("This is my content"), //actions:<Widget>[ //new FlatButton(child:new Text("CANCEL"), onPressed: (){ //Navigator.of(context).pop(); // //},), //new FlatButton(child:new Text("OK"), onPressed: (){ //Navigator.of(context).pop(); // //},) //] // //)); }
打印结果:
error1 Navigator operation requested with a context that does not include a Navigator. error2 Navigator operation requested with a context that does not include a Navigator. Navigator.of(context, rootNavigator=false, nullOk=true)=null
显然,无论怎么改rootNavigator和nullOk的值,Navigator.of(context, rootNavigator, nullOk)的值都是null。
为什么呢?
rootAncestorStateOfType函数的实现位于framework.dart里,我们可以看一下ancestorStateOfType和rootAncestorStateOfType的区别:
@override State ancestorStateOfType(TypeMatcher matcher) { assert(_debugCheckStateIsActiveForAncestorLookup()); Element ancestor = _parent; while (ancestor != null) { if (ancestor is StatefulElement && matcher.check(ancestor.state)) break;ancestor = ancestor._parent; } final StatefulElement statefulAncestor = ancestor; return statefulAncestor?.state; }@override State rootAncestorStateOfType(TypeMatcher matcher) { assert(_debugCheckStateIsActiveForAncestorLookup()); Element ancestor = _parent; StatefulElement statefulAncestor; while (ancestor != null) { if (ancestor is StatefulElement && matcher.check(ancestor.state)) statefulAncestor = ancestor; ancestor = ancestor._parent; } return statefulAncestor?.state; }
可以看出:
ancestorStateOfType的作用是: 如果某个父元素满足一定条件, 则返回这个父节点的state属性;
rootAncestorStateOfType的作用是: 返回最顶层的满足一定条件的父元素。
这个条件是: 这个元素必须属于StatefulElement , 而且其state属性与参数里的TypeMatcher 相符合。
查询源码可以知道:StatelessWidget 里的元素是StatelessElement,StatefulWidget里的元素是StatefulElement。
也就是说,要想让context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())的返回值不为null, 必须保证context所在的Widget的顶层Widget属于StatefulWidget(注意是顶层Widget,而不是自己所在的widget。如果context所在的Widget就是顶层Widget,也是不可以的)。
这样我们就大概知道为什么会出错了。我们的showAlertDialog方法所用的context是属于MyApp的, 而MyApp是个StatelessWidget。
那么,修改方案就比较清晰了,我们的对话框所使用的context不能是顶层Widget的context,同时顶层Widget必须是StatefulWidget。
修改后的完整代码如下:
import 'package:flutter/material.dart';main() { runApp(new MyApp()); }class MyApp extends StatefulWidget { @override State<StatefulWidget> createState() { return new MyState(); } } class MyState extends State<MyApp>{ @override Widget build(BuildContext context) { return new MaterialApp( title: 'Test', home: new Scaffold( appBar: new AppBar(title: new Text('Test')), body: new StatelessWidgetTest(), ), ); }} class StatelessWidgetTest extends StatelessWidget { @override Widget build(BuildContext context) { return _buildCenterButton(context); } } Widget _buildCenterButton(BuildContext context) { return new Container( alignment: Alignment.center, child: new Container( child: _buildButton(context), )); }Widget _buildButton(BuildContext context) { return new RaisedButton( padding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //padding child: new Text( 'show dialog', style: new TextStyle( fontSize: 18.0, //textsize color: Colors.white, // textcolor ), ), color: Theme.of(context).accentColor, elevation: 4.0, //shadow splashColor: Colors.blueGrey, onPressed: () { showAlertDialog(context); }); } void showAlertDialog(BuildContext context) { NavigatorState navigator= context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>()); debugPrint("navigator is null?"+(navigator==null).toString()); showDialog( context: context, builder: (_) => new AlertDialog( title: new Text("Dialog Title"), content: new Text("This is my content"), actions:<Widget>[ new FlatButton(child:new Text("CANCEL"), onPressed: (){ Navigator.of(context).pop();},), new FlatButton(child:new Text("OK"), onPressed: (){ Navigator.of(context).pop();},) ] )); }
实验结果:
screen1.png至于为什么flutter里的对话框控件对BuildContext的要求这么严格,暂时还不清楚原因。
在flutter里,Widget,Element和BuildContext之间的关系是什么呢?
摘抄部分系统源码如下:
abstract class Element extends DiagnosticableTree implements BuildContext{....}abstract class ComponentElement extends Element {}class StatelessElement extends ComponentElement { @override Widget build() => widget.build(this); }class StatefulElement extends ComponentElement { @override Widget build() => state.build(this); }abstract class Widget extends DiagnosticableTree { Element createElement(); }abstract class StatelessWidget extends Widget { @override StatelessElement createElement() => new StatelessElement(this); @protected Widget build(BuildContext context); }abstract class StatefulWidget extends Widget { @override StatefulElement createElement() => new StatefulElement(this); @protected State createState(); } abstract class State<T extends StatefulWidget> extends Diagnosticable { @protected Widget build(BuildContext context); }
日期:2018-10 浏览次数:7132
日期:2018-12 浏览次数:4198
日期:2018-07 浏览次数:4756
日期:2018-12 浏览次数:4045
日期:2018-09 浏览次数:5378
日期:2018-12 浏览次数:9793
日期:2018-11 浏览次数:4692
日期:2018-07 浏览次数:4461
日期:2018-05 浏览次数:4744
日期:2018-12 浏览次数:4197
日期:2018-10 浏览次数:5017
日期:2018-12 浏览次数:6090
日期:2018-11 浏览次数:4339
日期:2018-08 浏览次数:4458
日期:2018-11 浏览次数:12483
日期:2018-09 浏览次数:5447
日期:2018-12 浏览次数:4719
日期:2018-10 浏览次数:4058
日期:2018-11 浏览次数:4411
日期:2018-12 浏览次数:5936
日期:2018-06 浏览次数:3879
日期:2018-08 浏览次数:5318
日期:2018-10 浏览次数:4348
日期:2018-12 浏览次数:4356
日期:2018-07 浏览次数:4235
日期:2018-12 浏览次数:4385
日期:2018-06 浏览次数:4263
日期:2018-11 浏览次数:4247
日期:2018-12 浏览次数:4135
日期:2018-12 浏览次数:5151
Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.