【问题标题】:Best practice of async call on flutter?颤振异步调用的最佳实践?
【发布时间】:2019-01-29 08:04:01
【问题描述】:

我不希望每次重建小部件时都触发异步调用。 所以,我在 initState 中调用了 async 函数。

  1. 在 initState 中调用异步

    @override
    void initState() {
      someAsyncCall().then((result) {
        setState() {
         _result = result;
        }
      });
    }
    
    @override
    Widget build(BuildContext context) {
      if (_result == null) {
        reutrn new Container();
      }
    
      return new SomeWidget();
    }
    
  2. 使用 FutureBuilder

    @override
    void initState() {
      _future = someAsyncCall();
    }
    
    @override
    Widget build(BuildContext context) {
      return new FutureBuilder(
        future: _future,
        builder: // do something
      );
    }
    

这两种解决方案是否有任何副作用或不良做法?


我从 Flutter 修改了演示应用程序,以解释为什么我在 initSate 中调用异步。
1.打开app,打印main
2.推屏测试,打印测试,main
3.弹出测试,打印main

如果我在构建中调用异步函数,它会调用 3 次。
我想要的是,我需要一次异步函数调用,除非主要的 dispose / pop。
根据 Rémi Rousselet 的回答,在 initState 中调用 async 有点问题或错误。
那么,如何确保异步函数调用一次呢?

   @override
   Widget build(BuildContext context) {
    print("build main");
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new RaisedButton(onPressed: () {
              Navigator.push(context, new MaterialPageRoute(builder: (context) {
                return new Test();
              }));
            }),
          ],
        ),
      )
    );
  }

 class Test extends StatelessWidget {

   @override
   Widget build(BuildContext context) {
     print("build Test");
     return new Scaffold(
       body:
       new FlatButton(onPressed: () {
         Navigator.pop(context);
       }, child: new Text("back"))
     );
   }

 }

我需要做类似的事情吗

@override
Widget build(BuildContext context) {
  if (!mounted) {
    print("build main");
    // aysnc function call here
  }
}

【问题讨论】:

    标签: flutter


    【解决方案1】:

    虽然有点冗长,但FutureBuilder 绝对是要走的路。

    在 initState 中调用异步函数,然后在完成时执行 setState 会出现一些问题。在大多数情况下,您无需担心错误捕获或处理加载时间。

    请记住,Flutter 期望 initState 是同步的。因此,虽然 dart 不会阻止您对同步函数进行异步调用,但 Flutter 很可能会认为您的小部件已准备就绪,而实际上它还没有。

    【讨论】:

    • 如果在 initState 中调用异步函数不鼓励或错误,这意味着我需要在构建中调用这些函数?或者将未来存储在 initState 中是可以接受的(然后不调用)?它给服务器 API 带来了沉重的负担。此外,由于异步调用再次触发,当我按下返回按钮时,滚动偏移量将被重置。
    • 不要混淆initStatecreateStatebuild;每次小部件树更改时,它们都不会被 all 调用。 createState 被调用来创建状态,initState 是一次性初始化,所以这些只会被调用一次。每当小部件或父小部件状态从调用setState 更改时,就会调用构建。有关生命周期的详细信息,请参阅文档:docs.flutter.io/flutter/widgets/State-class.html
    • 我在阅读此文档后更新了问题。不确定用法是否正确。
    【解决方案2】:

    我会选择第一种样式,因为我可以控制等待时发生的事情。这取决于您希望从应用中获得什么,我更喜欢清晰的“正在加载”消息,而不是不知道它是卡住还是加载。

    说到副作用,你可以看看他们对FutureBuilder的评价

    这样做的副作用是,为 FutureBuilder 提供一个新的但已经完成的未来将导致单帧处于 ConnectionState.waiting 状态。这是因为无法同步确定 Future 已经完成。

    【讨论】:

      【解决方案3】:

      我可以在异步函数结束时执行一个空的 setState()。

      这将产生排队写入的效果,这样您就只能在所有异步工作完成后才能看到它们的视觉效果。但是,队列不可靠。您的应用中的其他内容可能会导致小部件重建,在这种情况下,您将看到各个写入的效果。

      或者我可以将每个单独的分配包装到它自己的 setState 中:为 await getCustomValue() 使用一个临时值;然后将其分配给 setState 内的值。

      这将确保您看到每个增量状态更改的视觉更新(假设异步任务之间有足够的时间来建立一个框架)。

      我应该选择哪个? 中间状态对用户有意义吗?具体来说,您是否要确保在您完成这一系列操作时,您的应用程序更新的视觉效果会出现?反之,是否要阻止向用户显示中间状态?

      如果您希望用户看到中间状态,您可能希望将每个写入包装在一个 setState 调用中。如果您想防止中间状态可见,您可能希望实际对写入进行排队,并让它们在一系列异步操作结束时自动发生。

      将单个 setState 放在函数的末尾是两全其美的事情,因为中间状态将任意可见,具体取决于您的应用程序中发生的其他情况。在最坏的情况下,中间状态可能有问题并且难以重现。

      有没有更好的解决方案?

      假设您想等待所有操作完成,然后自动更新对象的状态,我建议让异步对象返回所有状态更改的表示。异步操作完成后,我将在单个 setState 调用中同步提交对您的状态的所有更改。这将产生以原子方式更改您的状态并确保更新您的应用的视觉外观的效果。

      另一个需要考虑的问题是,如果在异步操作挂起时从树中移除小部件会发生什么情况。理想情况下,您应该取消异步操作以节省资源,但如果这不是一个选项,您可能需要在提交更改之前检查 State 对象上已安装的布尔值。

      来自:3951#issue

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-09
        • 1970-01-01
        • 2018-02-14
        • 1970-01-01
        • 2011-08-12
        • 2013-02-02
        相关资源
        最近更新 更多