Dart/Flutter 在使用 async/await 时出现干净代码的问题

问题描述 投票:0回答:1

我正在通过开发一个小型 Android 应用程序来学习 Dart/Flutter。当我试图理解 async 和 await 在 Dart/Flutter 中的工作原理时,我经常发现自己在尝试使用它和编写干净的代码时遇到困难。这是一个简短的例子:

// ...

void main() {
  runApp(const RootWidget());
}

class RootWidget extends StatelessWidget {
  const RootWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // ...
      home: CameraWidget(),
    );
  }
}

class CameraWidget extends StatefulWidget {
  late final CameraDescription camera;

  CameraWidget({super.key}) {
    final cameras = await availableCameras(); // <-- ERROR
    camera = cameras.first;
  }

  // ...
}

如您在示例中所见,我需要从构造函数内部调用异步函数,这是不允许的并且会产生错误。我研究了如何规避这个问题并找到了 2 种可能的解决方案,但这些解决方案在这里都不起作用:

工厂

一个建议的解决方案是使用创建 CameraWidget 类实例的工厂函数。但是这样做有个问题,工厂函数本身也需要标记为async,因为它需要使用await来等待返回的availableCameras的List。然后必须使用此工厂方法代替 RootWidget 的构建函数中的常规构造函数,这也会将构建函数变成异步函数。但这是不可能的,因为我们无法更改构建函数的函数签名。

然后

我发现的另一个解决方案是像这样使用then函数:

CameraWidget({super.key}) {
  availableCameras().then((cameras) {
    camera = cameras.first;
  });
}

但这不会阻止执行,并且在使用相机实例变量之前未设置相机实例变量。


我找到的唯一可行的解决方案是在主函数中获取 CameraDescription,将其提供给 RootWidget 的构造函数,这也将其提供给 CameraWidget 的构造函数。

但这是一个非常肮脏的解决方案,因为初始化相机实例变量的代码位于主函数内部,它不属于该位置。恕我直言,初始化相机实例变量的代码属于 CameraWidget 的构造函数。我的意思是,这基本上就是构造函数的用途,对吧?

这不是我第一次在 Dart/Flutter 中遇到异步/等待问题,我需要另一个函数 B 中的异步函数 A 的值,但我无法将函数 B 标记为异步,我不得不解决这个问题。这真的很烦人,因为我只想要一个函数调用的返回值,我需要添加很多额外的步骤才能将它送到我需要的地方,只是因为它是异步的。

关于 async/await,我做错了什么?对于给出的示例,什么是更清晰的解决方案?

flutter dart async-await
1个回答
0
投票

您是正确的,在这种情况下使用工厂方法或 then 不是最佳解决方案。通常,不建议在构造函数内部执行异步操作,因为构造函数应该是快速且同步的。要解决此问题,您可以在创建小部件后使用 initState 方法初始化 CameraWidget 状态。这是一个如何修改代码以使用 initState 的示例:

// ...

void main() {
  runApp(const RootWidget());
}

class RootWidget extends StatelessWidget {
  const RootWidget({Key? key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // ...
      home: CameraWidget(),
    );
  }
}

class CameraWidget extends StatefulWidget {
  const CameraWidget({Key? key}) : super(key: key);

  @override
  _CameraWidgetState createState() => _CameraWidgetState();
}

class _CameraWidgetState extends State<CameraWidget> {
  late CameraDescription camera;

  @override
  void initState() {
    super.initState();
    availableCameras().then((cameras) {
      setState(() {
        camera = cameras.first;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    if (camera == null) {
      return Center(child: CircularProgressIndicator());
    } else {
      return CameraPreview(CameraController(camera, ResolutionPreset.medium));
    }
  }
}

在修改后的代码中,我们创建了一个单独的 _CameraWidgetState 类来扩展 State。在这个状态类中,我们定义了一个相机变量,一旦从 availableCameras 函数中检索到它,它就会保存 CameraDescription。我们覆盖了 initState 方法,该方法在创建小部件后调用,并在其中调用 availableCameras().then 来检索相机并在结果可用后设置相机变量。我们还调用 setState 来使用更新的相机变量重建小部件树。最后,在构建方法中,我们检查相机变量是否为 null,如果是,则返回 CircularProgressIndicator,如果不是,则返回 CameraPreview 小部件。

使用 initState 比使用工厂方法或在构造函数中使用 initState 更干净。它允许您在不阻塞构造函数且不更改其他方法的函数签名的情况下执行异步操作。注意,在使用 initState 时,你应该总是在方法的开头调用 super.initState()

© www.soinside.com 2019 - 2024. All rights reserved.