不要跨异步间隔使用 BuildContext

我注意到在我的项目中有一个新的线头问题。

长话短说:

我需要在自定义类中使用 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
}
}

更新日期: 2022年9月17日

看起来 BuildContext 很快就会有一个“挂载”属性

所以你可以这样做:

if (context.mounted)

它基本上也允许 StatelessWidgets 检查“挂载”。

参考资料: Remi Rousselet 推特

43632 次浏览

不要将上下文直接存储到定制类中,如果不确定挂载了小部件,则不要在异步之后使用上下文。

做这样的事情:

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),
);
}
}

只要保存你的导航器或任何需要在函数开始的变量上下文

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

不要跨异步间隔使用 BuildContext。

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

如果从 StatefutionWidget 使用 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();
}

如果类可以从 StatefulWidget扩展,则添加

if (!mounted) return;

会成功的

剪辑

我一次又一次地遇到这个问题,下面是在使用异步方法之前使用 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();
}
}

剪辑结束

根据吉尔登的回答,他仍然使用

if (!mounted) return;

那么添加更多带有回调的意大利面代码有什么意义呢?如果这个 async方法必须将一些数据传递给同样要传递 context的方法,该怎么办?然后,我的朋友,你会有更多的意大利面在桌子上和另一个额外的问题。< br > < br >

核心概念是在触发异步块之后不使用 context;)

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

void onButtonTapped(BuildContext context) {

(上下文) . pop () ; }

StatefulWidget中,使用:

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

StatelessWidget或任何其他类中,尝试这种方法:

class Foo {
void bar(BuildContext context, [bool mounted = true]) async {
await yourFuture();
if (!mounted) return;
Navigator.pop(context);
}
}

你可以用这个方法

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

为了避免在 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),
))
],
);
}
}


如果您想在无状态小部件中使用安装的检查,可以在 BuildContext 上进行扩展

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

然后你可以像这样使用它

if (context.mounted)

灵感来自于 此特性的 GitHub PR,并且在合并后的 PR 中通过了相同的测试