我注意到我的项目中有一个新的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();
}