不要跨异步间隙使用BuildContexts



我注意到我的项目中有一个新的lint问题。

长话短说:

我需要使用BuildContext在我的自定义类

与aysnc方法一起使用时,颤振绒工具不高兴。

的例子:

MyCustomClass{
final buildContext context;
const MyCustomClass({required this.context});
myAsyncMethod() async {
await someFuture();
# if (!mounted) return;          << has no effect even if i pass state to constructor
Navigator.of(context).pop(); #   << example
}
}

更新颤振3.7 +:

mounted属性现在正式添加到BuildContext中,因此您可以从任何地方检查它,无论是来自StatefulWidget状态还是来自Stateless widget。

虽然将上下文存储到外部类中仍然是一个不好的做法,但现在您可以在异步调用后安全地检查它,如下所示:

class MyCustomClass {
const MyCustomClass();
Future<void> myAsyncMethod(BuildContext context) async {
Navigator.of(context).push(/*waiting dialog */);
await Future.delayed(const Duration(seconds: 2));
if (context.mounted) Navigator.of(context).pop();
}
}
// Into widget
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () => const MyCustomClass().myAsyncMethod(context),
icon: const Icon(Icons.bug_report),
);
}
// Into widget

原始回答

不要将context直接存储到自定义类中,如果你不确定你的小部件是否挂载,也不要在async之后使用context。

像这样做:

class MyCustomClass {
const MyCustomClass();
Future<void> myAsyncMethod(BuildContext context, VoidCallback onSuccess) async {
await Future.delayed(const Duration(seconds: 2));
onSuccess.call();
}
}
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () => const MyCustomClass().myAsyncMethod(context, () {
if (!mounted) return;
Navigator.of(context).pop();
}),
icon: const Icon(Icons.bug_report),
);
}
}

使用context.mounted*

StatefulWidget/StatelessWidget或任何具有BuildContext的类中:

void foo(BuildContext context) async {
await someFuture();
if (!context.mounted) return;
Navigator.pop(context); // No warnings now
}

*如果您在StatefulWidget中,您也可以使用mounted而不是context.mounted

如果您的类可以从StatefulWidget扩展,那么添加

if (!mounted) return;

会工作!

编辑

我一次又一次地遇到这个问题,这里有一个技巧-在使用async方法之前使用context使用或声明变量,如下所示:

MyCustomClass{
const MyCustomClass({ required this.context });
final buildContext context;

myAsyncMethod() async {
// Declare navigator instance (or other context using methods/classes)
// before async method is called to use it later in code
final navigator = Navigator.of(context);

await someFuture();

// Now use the navigator without the warning
navigator.pop();
}
}

编辑结束

根据Guildem的回答,他仍然使用

if (!mounted) return;

那么添加更多带有回调的面条式代码有什么意义呢?如果这个async方法必须将一些数据传递给您也传递给context的方法,该怎么办?那么,我的朋友,你的桌上就会有更多的意大利面和另一个额外的问题。

核心概念是在async block被触发后不要使用context;)

如果你想在无状态小部件中使用挂载检查,可以在BuildContext上做一个扩展

extension ContextExtensions on BuildContext {
bool get mounted {
try {
widget;
return true;
} catch (e) {
return false;
}
}
}

然后你可以像这样使用

if (context.mounted)

灵感来自GitHub PR这个功能,它通过了相同的测试在合并的PR

您可以使用这种方法

myAsyncMethod() async {
await someFuture().then((_){
if (!mounted) return;          
Navigator.of(context).pop(); 
}
});

在Flutter 3.7.0中BuildContext具有mounted属性。它可以在StatelessWidget和statfulwidgets中使用,如下所示:

void bar(BuildContext context) async {
await yourFuture();
if (!context.mounted) return;
Navigator.pop(context);
}

这对我有帮助。

/// transition to the main page after a pause of 3 seconds
Future<void> _navigateToMainScreen(BuildContext context) async {
await Future.delayed(const Duration(seconds: 3));
if (context.mounted) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => const MainScreen()));
}
}

要在StatelessWidget中避免这种情况,您可以参考以下示例

class ButtonWidget extends StatelessWidget {
final String title;
final Future<String>? onPressed;
final bool mounted;
const ButtonWidget({
super.key,
required this.title,
required this.mounted,
this.onPressed,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
const SizedBox(height: 20),
Expanded(
child: ElevatedButton(
onPressed: () async {
final errorMessage = await onPressed;
if (errorMessage != null) {
// This to fix: 'Do not use BuildContexts across async gaps'
if (!mounted) return;
snackBar(context, errorMessage);
}
},
child: Text(title),
))
],
);
}
}

在当前版本的flutter中不能跨异步方法或类使用buildcontext而不是在async await中引入上下文

使用方法回调用于导航和其他目的

只需简化创建一个函数来调用导航

void onButtonTapped(BuildContext context) {
Navigator.of(context).pop();
}

将导航器或其他需要上下文的内容保存到函数开头的变量中

myAsyncMethod() async {
final navigator = Navigator.of(context); // 1
await someFuture();
navigator.pop();  // 2
}

不要在异步间隙中使用BuildContext。

存储BuildContext以供以后使用很容易导致难以诊断崩溃。异步间隙隐式地存储BuildContext,是编写代码时最容易忽略的。

当从StatefulWidget中使用BuildContext时,必须在异步间隔后检查所挂载的属性。

所以,我认为,你可以这样使用:

好:

class _MyWidgetState extends State<MyWidget> {
...
void onButtonTapped() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return;
Navigator.of(context).pop();
}
}

坏:

void onButtonTapped(BuildContext context) async {
await Future.delayed(const Duration(seconds: 1));
Navigator.of(context).pop();
}

最新更新