flutter - 异步等待 Navigator.push() - 出现 linter 警告 : use_build_context_synchronously

标签 flutter dart asynchronous lint flutter-navigation

在 Flutter 中,所有 Navigator将新元素插入导航堆栈的函数返回 Future因为调用者有可能等待执行并处理结果。
我大量使用它 e。 G。将用户(通过 push() )重定向到新页面时。当用户完成与该页面的交互时,我有时希望原始页面也pop() :

onTap: () async {
  await Navigator.of(context).pushNamed(
    RoomAddPage.routeName,
    arguments: room,
  );

  Navigator.of(context).pop();
},
一个常见的例子是使用带有敏感操作(如删除实体)按钮的底部工作表。当用户单击按钮时,会打开另一个底部工作表,要求确认。当用户确认时,确认对话框以及打开确认底部工作表的第一个底部工作表将被关闭。
所以基本上是onTap底部工作表内的 DELETE 按钮的属性如下所示:
onTap: () async {
  bool deleteConfirmed = await showModalBottomSheet<bool>(/* open the confirm dialog */);
  if (deleteConfirmed) {
    Navigator.of(context).pop();
  }
},
这种方法一切都很好。我唯一的问题是 linter 发出警告:use_build_context_synchronously因为我使用相同的 BuildContext完成后一个async功能。
忽略/暂停此警告对我来说安全吗?但是我将如何等待导航堆栈上的推送操作以及我使用相同的后续代码 BuildContext ?有没有合适的替代方案?必须有这样做的可能性,对吧?
PS:我不能也不想检查 mounted属性,因为我没有使用 StatefulWidget .

最佳答案

简答:
始终忽略此警告是不安全的,即使在无状态小部件中也是如此。
这种情况下的解决方法是使用 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(有状态小部件)中,我每 500 毫秒刷新一次,因此经常调用构建函数。然后我做了 2 个按钮,都打开一个对话框,然后尝试弹出当前页面(就像你在问题中描述的那样)。其中一个在打开对话框之前存储导航器,另一个在异步调用之后危险地使用 BuildContext (就像你在问题中描述的那样)。单击按钮后,如果您坐下来等待警报对话框几秒钟,然后退出它(通过单击对话框外的任意位置),更安全的按钮会按预期工作并弹出当前页面,而另一个按钮则不会。
它打印出来的错误是:

[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe. At this point the state of the widget's element tree is no longer stable. To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method. #0 Element._debugCheckStateIsActiveForAncestorLookup. (package: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. (package: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();
      },
    );
  }
}

关于flutter - 异步等待 Navigator.push() - 出现 linter 警告 : use_build_context_synchronously,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69466478/

相关文章:

flutter - 使用 Streambuilder 包装 CustomScrollView 和 Sliverlist 会引发异常

android - 滑动列表项以获取更多选项(Flutter)

android - 在不创建帐户的情况下使用 SyncAdapter

dart - 无需对话的 Flutter local_auth 指纹认证

ios - 如何在 flutter 中启动具有多个停靠点的苹果 map

flutter - 当用户点击封面时,我想查看PDF文件

google-maps - 如何从纬度和经度中提取位置名称

javascript - 可观察对象存储在浏览器内存中的什么位置?

python - 我应该为 aiohttp 和 sanic 等异步框架使用单独的 WSGI 服务器吗?

Flutter 排序数据 Firestore 与 Streambuilder