不要跨异步间隙使用 BuildContext

Ber*_*eam 240 dart flutter flutter-dependencies flutter-state flutter-build

我注意到我的项目中出现了新的 lint 问题。

长话短说:

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

flutter lint 工具与 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
      }
   }
Run Code Online (Sandbox Code Playgroud)

Gui*_*dem 279

更新 Flutter 3.7+

mounted属性现已正式添加到BuildContext,因此您可以从任何地方检查它,无论它来自 StatefulWidget State 还是来自 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
Run Code Online (Sandbox Code Playgroud)

原答案

不要将上下文直接存储到自定义类中,如果您不确定您的小部件是否已安装,也不要在异步后使用上下文。

做这样的事情:

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),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 如何在 StatelessWidget 中做到这一点? (25认同)
  • @stk在无状态小部件中,您可以使用此:/sf/answers/4865888471/ (3认同)
  • @stk我不认为你可以,StatelessWidget实际上没有生命周期(没有状态)。但是,如果您想要一个异步方法,那么您需要做一件很长的事情,并且您希望应用程序用户看到正在发生的事情,因此您需要一个状态。如果您查看您的应用程序,您应该会看到需要在哪里处理此状态并创建一个有状态小部件。 (2认同)
  • @stk 答案更新为 Flutter 3.7+ 上的无状态小部件 (2认同)

iDe*_*ode 100

使用context.mounted*

StatefulWidget/StatelessWidget或任何具有以下特征的类中BuildContext

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

* 如果你在 a 中StatefulWidget,你也可以使用 justmounted代替context.mounted

  • @BeniaminoBaggins `StatelessWidget` 总是被挂载的,所以你永远不必在其中传递 `mounted: false` ,但为了摆脱警告,最好将一个名为 `mounted` 的字段设置为 `true`。 (3认同)
  • @BeniaminoBaggins 这整个解决方案/概念在文档中找不到(据我所知)。文档可能建议您将“StatelessWidget”转换为“StatefulWidget”,然后使用“mounted”标志来检查小部件是否在树中。 (2认同)
  • @Aseem 使用“flutter Upgrade”更新您的 Flutter 版本。 (2认同)

小智 40

如果你的课程可以StatefulWidget从那时开始扩展

if (!mounted) return;
Run Code Online (Sandbox Code Playgroud)

会工作!

编辑

我一次又一次地遇到这个问题,这就是技巧 - 在使用异步方法之前使用或声明变量,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();
      }
    }
Run Code Online (Sandbox Code Playgroud)

编辑结束

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

if (!mounted) return;
Run Code Online (Sandbox Code Playgroud)

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

context核心概念是触发 async bloc 后不使用;)

  • 这个解决方案不起作用。它的问题与直接从异步方法调用“Navigator.of(context).pop()”完全相同。如果这隐藏了相关的分析器警告,那么它只是分析器中的一个错误。 (6认同)

Mun*_*Ali 9

你可以使用这种方法

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

现在,您可以从小部件中的任何位置以及使用 BuildContext 的任何类中的上下文中访问已安装的内容,如下所示

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


小智 9

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

extension ContextExtensions on BuildContext {
  bool get mounted {
    try {
      widget;
      return true;
    } catch (e) {
      return false;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它

if (context.mounted)
Run Code Online (Sandbox Code Playgroud)

此功能的灵感来自GitHub PR ,并且在合并的 PR 中通过了相同的测试


Иль*_*Кок 9

这对我有帮助。

/// 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()));
  }
}
Run Code Online (Sandbox Code Playgroud)


Yay*_*ano 8

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

void bar(BuildContext context) async {
  await yourFuture();
  if (!context.mounted) return;
  Navigator.pop(context);
}
Run Code Online (Sandbox Code Playgroud)