异步等待 Navigator.push() - 出现 linter 警告:use_build_context_synchronously
Posted
技术标签:
【中文标题】异步等待 Navigator.push() - 出现 linter 警告:use_build_context_synchronously【英文标题】:Waiting asynchronously for Navigator.push() - linter warning appears: use_build_context_synchronously 【发布时间】:2021-11-26 16:17:26 【问题描述】:在 Flutter 中,所有将新元素推送到导航堆栈的 Navigator
函数都会返回 Future
,因为调用者可以等待执行并处理结果。
我大量使用它 e. G。将用户(通过push()
)重定向到新页面时。当用户完成与该页面的交互时,我有时希望原始页面也可以pop()
:
onTap: () async
await Navigator.of(context).pushNamed(
RoomAddPage.routeName,
arguments: room,
);
Navigator.of(context).pop();
,
一个常见的例子是使用带有敏感操作按钮的底部工作表(如删除实体)。当用户点击按钮时,另一个底部工作表被打开,要求确认。当用户确认时,确认对话框以及打开确认底页的第一个底页将被关闭。
所以基本上底部表格内的 DELETE 按钮的onTap
属性如下所示:
onTap: () async
bool deleteConfirmed = await showModalBottomSheet<bool>(/* open the confirm dialog */);
if (deleteConfirmed)
Navigator.of(context).pop();
,
这种方法一切正常。我唯一的问题是 linter 会发出警告:use_build_context_synchronously,因为我在完成 async
函数后使用了相同的 BuildContext
。
忽略/暂停此警告对我来说是否安全?但是我将如何等待导航堆栈上的推送操作以及我使用相同BuildContext
的后续代码?有合适的替代方案吗?一定有可能做到这一点,对吧?
PS:我不能也不想检查mounted
属性,因为我没有使用StatefulWidget
。
【问题讨论】:
【参考方案1】:简答:
始终忽略此警告是不安全的,即使在无状态小部件中也是如此。
在这种情况下,一种解决方法是在异步调用之前使用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();
,
);
【讨论】:
出色的解释和易于理解的最小示例!谢谢你。赏金是你的:)。但是有一个问题:您将解决方案标记为“快速解决方法”。这是否意味着有更清洁的解决方案? @Schnodderbalken 哈哈,我主要关注的是“快速”这个词——在你的情况下(获取导航器),这已经是一个非常干净的解决方案。然而,我们可能并不总是这么幸运。例如,如果您有一个将 BuildContext 作为参数的自定义函数,则此“解决方法”将不起作用,因此您可能必须进行一些不那么快速的重构或考虑另一种解决方法。顺便说一句,很好的问题,这是我第一次听说这个 lint 规则。希望此 lint 作为默认规则发布后,您的问题可以帮助更多人。 我正在使用名为lint
: pub.dev/packages/lint 的社区驱动的lint 包。这个包比官方的包更严格,所以我遇到了这个警告。无论如何,再次感谢你。现在有赏金;)以上是关于异步等待 Navigator.push() - 出现 linter 警告:use_build_context_synchronously的主要内容,如果未能解决你的问题,请参考以下文章
在 Flutter 中使用 Navigator.push() 时出现黑屏
Dart / Flutter:在没有上下文的页面之间导航(使用 Navigator)(所以没有 Navigator.push?)
FLUTTER:-我需要从Navigator.push()方法之后的方法返回小部件