简答:
始终忽略此警告是不安全的,即使在无状态小部件中也是如此。
在这种情况下,一种解决方法是在异步调用之前使用context。例如,找到Navigator 并将其存储为变量。这样,您将传递 Navigator,而不是传递 BuildContext,如下所示:
onPressed: () async {
final navigator = Navigator.of(context); // store the Navigator
await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Dialog Title'),
),
);
navigator.pop(); // use the Navigator, not the BuildContext
},
长答案:
此警告实质上是在提醒您,在异步调用之后,BuildContext 可能 不再有效。 BuildContext 失效有几个原因,例如,在等待期间销毁原始小部件可能是(主要)原因之一。这就是为什么最好检查您的有状态小部件是否仍然挂载的原因。
但是,我们无法检查无状态小部件上的mounted,但这绝对不意味着它们不能在等待期间被卸载。 如果满足条件,它们也可以卸载!例如,如果它们的父小部件是有状态的,如果它们的父小部件在等待期间触发了重建,并且如果无状态小部件的参数以某种方式发生了更改,或者如果它的密钥不同,它将被销毁并重新创建。这将使旧的 BuildContext 无效,如果您尝试使用旧的上下文,则会导致崩溃。
为了演示危险,我创建了一个小项目。在TestPage(Stateful Widget)中,我每500毫秒刷新一次,所以build函数被频繁调用。然后我做了2个按钮,都打开一个对话框然后尝试弹出当前页面(就像你在问题中描述的那样)。其中一个在打开对话框之前存储导航器,另一个在异步调用之后危险地使用 BuildContext (就像您在问题中描述的那样)。单击按钮后,如果您在警报对话框上等待几秒钟,然后退出它(通过单击对话框外的任何位置),更安全的按钮将按预期工作并弹出当前页面,而另一个按钮则不会。
它打印出来的错误是:
[VERBOSE-2:ui_dart_state.cc(209)] 未处理的异常:查找
停用的小部件的祖先是不安全的。此时的状态
小部件的元素树不再稳定。为了安全地参考
小部件的祖先在其 dispose() 方法中,保存对
祖先通过调用dependOnInheritedWidgetOfExactType()
小部件的 didChangeDependencies() 方法。
#0 元素._debugCheckStateIsActiveForAncestorLookup。 (包:flutter/src/widgets/framework.dart:4032:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:4046:6)
#2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:4093:12)
#3 Navigator.of (package:flutter/src/widgets/navigator.dart:2736:40)
#4 MyDangerousButton.build。 (包:helloworld/main.dart:114:19)
演示问题的完整源代码:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: ElevatedButton(
child: Text('Open Test Page'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => TestPage()),
);
},
),
),
);
}
}
class TestPage extends StatefulWidget {
@override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late final Timer timer;
@override
void initState() {
super.initState();
timer = Timer.periodic(Duration(milliseconds: 500), (timer) {
setState(() {});
});
}
@override
void dispose() {
timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final time = DateTime.now().millisecondsSinceEpoch;
return Scaffold(
appBar: AppBar(title: Text('Test Page')),
body: Center(
child: Column(
children: [
Text('Current Time: $time'),
MySafeButton(key: UniqueKey()),
MyDangerousButton(key: UniqueKey()),
],
),
),
);
}
}
class MySafeButton extends StatelessWidget {
const MySafeButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Open Dialog Then Pop Safely'),
onPressed: () async {
final navigator = Navigator.of(context);
await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Dialog Title'),
),
);
navigator.pop();
},
);
}
}
class MyDangerousButton extends StatelessWidget {
const MyDangerousButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Open Dialog Then Pop Dangerously'),
onPressed: () async {
await showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Dialog Title'),
),
);
Navigator.of(context).pop();
},
);
}
}