likes
comments
collection
share

关于BuildContext的警告:Don't use 'BuildContext's across async gaps

作者站长头像
站长
· 阅读数 30

关于BuildContext的警告:Don't use 'BuildContext's across async gaps

Flutter 开发过程中,大家肯定遇到过这样的警告:Don't use 'BuildContext's across async gaps.

关于BuildContext的警告:Don't use 'BuildContext's across async gaps

今天我们就来讨论在 Flutter 开发中一个常见但经常被忽视的问题:在异步操作间隙中使用 BuildContext

这个警告是什么意思?

这个警告是在提醒开发人员避免在异步操作中使用 BuildContext。在 Flutter 中,BuildContext 是一个关键的参数,用于访问有关 widget 在树中的位置信息,在导航、显示对话框、访问主题数据等功能中起到重要作用。

但是,当我们在异步操作中传递 BuildContext 时(如在 FutureStreamBuilderIsloates 内),可能会导致问题。此警告建议不要这样做,因为它可能会导致应用出现意外和错误的行为。

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 按钮,会发现直接报错:

关于BuildContext的警告:Don't use 'BuildContext's across async gaps

原因很明显,传进去的 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
评论
请登录