关于BuildContext的警告:Don't use 'BuildContext's across async gaps
在 Flutter
开发过程中,大家肯定遇到过这样的警告:Don't use 'BuildContext's across async gaps.
今天我们就来讨论在 Flutter
开发中一个常见但经常被忽视的问题:在异步操作间隙中使用 BuildContext
。
这个警告是什么意思?
这个警告是在提醒开发人员避免在异步操作中使用 BuildContext
。在 Flutter
中,BuildContext
是一个关键的参数,用于访问有关 widget
在树中的位置信息,在导航、显示对话框、访问主题数据等功能中起到重要作用。
但是,当我们在异步操作中传递 BuildContext
时(如在 Future
、StreamBuilder
或 Isloates
内),可能会导致问题。此警告建议不要这样做,因为它可能会导致应用出现意外和错误的行为。
当 BuildContext
在异步间隙中使用时,它可能会指向不再存在的 widget
,从而导致以下问题:
- 过时数据:如果在异步操作过程中,正在进行重建或丢弃
widget
,则BuildContext
可能会指向过时或不存在的widget
,这可能会导致应用显示不正确或过时的数据。 - 内存泄漏:当
BuildContext
指向应该被销毁的widget
时,可能会导致内存泄漏,因为Flutter
框架无法对与该widget
相关的资源进行垃圾回收。 - 应用崩溃:在某些情况下,如果在异步操作完成之前,
BuildContext
指向已经销毁的widget
,甚至可能会导致应用崩溃。
来看个错误例子。
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
Future<void> _showDialogAfterDelay(BuildContext context) async {
await Future.delayed(const Duration(seconds: 2));
showDialog(
// warning: Don't use 'BuildContext's across async gaps
context: context,
builder: (context) => const AlertDialog(
title: Text('Hello'),
content: Text('This is a dialog'),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Second Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _showDialogAfterDelay(context),
child: const Text('Show Dialog'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go Back'),
)
],
),
),
);
}
}
在第二个页面中,有两个按钮,一个显示对话框,一个返回至上个页面。当点击 Show Dialog 按钮,然后再快速点击 Go Back 按钮,会发现直接报错:
原因很明显,传进去的 context
已经被销毁了。该例子只是为了说明可能会产生的错误,实际上我们很少会这么写代码的。
本质上,这个警告鼓励开发者认真考虑如何在异步操作中使用 BuildContext
,强调理解 widget
生命周期管理的重要性,避免可能影响 Flutter
应用程序可靠性和性能的常见陷阱。
解决方法
知道这个警告的意思之后,那我们该如何解决呢?有以下几种解决方法。
方法一:使用 GlobalKey
即使 widget
被销毁或者重建,这种方法也能确保正确的 BuildContext
与异步操作相关联。
Step 1:创建 GlobalKey
首先创建一个 GlobalKey
,并将其添加到异步操作的父级 widget
。这可确保从此 GlobalKey
检索到的 BuildContext
有效。
final GlobalKey<_MyWidgetState> myWidgetKey = GlobalKey();
Step 2:检索 GlobalKey
在异步操作中,您可以使用 GlobalKey
检索 BuildContext
。
Future<void> fetchData() async {
/// Getting the current context of the widget in the widget tree
final context = myWidgetKey.currentContext as BuilderContext;
final result = await Navigator.of(context).push(...);
if (context.mounted) {
// statements after async gap without warning
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$result'),
));
}
}
该方法可以确保 BuildContext
在异步操作期间始终保持有效。
优点
- 可靠的上下文:使用
GlobalKey
可保证关联的BuildContext
始终是最新且准确的。 - 可预测的行为:即使在异步操作期间,
widget
树仍与其各自的BuildContext
保持正确关联。 - 不易出错:这种方法减少了由于过时的
BuildContext
引用而导致错误和崩溃的可能性。
方法二:使用 then 方法
then
方法是处理需要使用有效 BuildContext
的异步操作的简单方法。它可确保您的代码仅在异步操作成功完成后才执行,从而提供对正确 BuildContext
的访问。以下是示例,说明如何实现此解决方案:
Future<void> fetchData() async {
await Navigator.of(context).pushNamed(...)
.then((result) {
// statements after async gap without warning
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('result'),
));
});
}
优点
- 一致的上下文:使用
then
可确保代码在与异步操作相同的执行上下文中执行,从而提供对可靠BuildContext
的访问。 - 清晰的流程:代码保持井然有序且直观,逻辑遵循顺序模式,使其更易于理解和维护。
当然如果能够确定确实不会引发问题,但是又实在无法忍受有警告的存在,我们可以在 linter
中加入这条规则。
linter:
rules:
- use_build_context_synchronously
转载自:https://juejin.cn/post/7392513761854341130