Flutter:从 UI 调用异步代码的最佳实践

Posted

技术标签:

【中文标题】Flutter:从 UI 调用异步代码的最佳实践【英文标题】:Flutter: Best Practices of Calling Async Code from UI 【发布时间】:2018-08-14 14:15:31 【问题描述】:

我正在开发一个 Flutter 应用程序,但想知道从 UI 调用异步代码时应该怎么做 - 比如说 UI 小部件的构建方法。

例如,我的应用通过服务类连接 Firebase,该服务类使用 Async-Await 样式从 Firebase 获取记录。使用 await 可确保我的服务类的方法在返回 UI 端之前完成记录检索。

但是,由于服务类的方法被标记为异步,它会立即向调用 UI 小部件返回 Future,并且 UI 代码会在服务类的方法完成之前继续运行。

是的,我可以为“then()”回调编写代码来处理检索到的记录,但是接下来的小部件内的其他代码呢,并且取决于异步调用的结果?这是否意味着我需要在“then()”子句中嵌入所有内容,并避免在返回 Future 的方法调用之后添加任何执行步骤?这种使用模式有什么建议的模式吗?

@override
Widget build(BuildContext context) 

   RealtimeDatabase.getData()  // my service class
      .then((onValue) 
        ..... // do something after the async call is completed
   

   ..... // do something immediately before the async call is done


class RealtimeDatabase 

    Future<String> getData() async 

    DataSnapshot dataSnapshot = await FirebaseDatabase.instance.reference().orderByKey().once(); // will wait until completed
    .....
    .....

对不起,如果我在这里的描述不够清晰,欢迎任何建议。

吉米

【问题讨论】:

理想情况下有一个变量isLoading = true,当为真时返回一个加载小部件。在 then 块中调用 setStateisLoading false。 @Hemanth 听起来不错! 【参考方案1】:

在 Flutter 中,有一些小部件可以帮助您无缝地执行此操作(例如,FutureBuilderStreamBuilder),您可以根据它们的分辨率控制要渲染的内容。

FutureBuilder 上的示例:

Widget build(BuildContext context) 
    return new FutureBuilder(
    future: FirebaseDatabase.instance.reference().child("node"),
    builder: (BuildContext context, AsyncSnapshot snapshot) 
          return snapshot.hasData? new Scaffold(
          ///start building your widget tree
          ):new CircularProgressIndicator(); ///load until snapshot.hasData resolves to true
,);
  

StreamBuilder 的示例:

class Database 

  DatabaseReference _refProfile = FirebaseDatabase.instance.reference().child(
      "profiles");

  getProfiles() => _refProfile.onValue; 

......

Widget build(BuildContext context) 
      return new StreamBuilder<Event>(
          stream: _database.getProfiles(), //_database = new Database()
          builder: (BuildContext context, AsyncSnapshot<Event> event) 
          return event.hasData?new Scaffold(

///build your widget tree
                    ):new CircularProgressIndicator();

/// place holder

值得一提的是

FutureBuilder 更适合用于获取一些数据一次并且不关心保持一致的连接或跟踪数据中的任何更改。

而另一方面,StreamBuilder 使您能够继续监听数据,并且您可以根据数据中的任何更新来更新 UI 的状态。

【讨论】:

太棒了!!这就是我要找的! 我已经编辑了答案以包含 StreamBuilder 的示例,以及文档的链接。 非常感谢,折腾了几天,不知道 Flutter 有这么好的功能! 似乎返回语句缺少 StreamBuilder 示例。我想知道在“构建你的小部件树”评论的地方会发生什么。例如,它应该如何在 ListView 中显示流配置文件所产生的... @Wonderjimmy 这是我的错,我应该更清楚一点,你永远不想在 Scaffold 级别上做 FutureBuilder,而是在 body 属性中做。这样,您的 appbar、drawer、FAB、tabs..etc 将正常加载,并且只有包含内容的主体才会加载。【参考方案2】:

虽然我认为@aziza 提到的方法绝对是可行的方法,但值得注意的是,您也可以“自己动手”。

因此,例如,在initState() 中,您可以执行以下操作:

@override
void initState() 
    super.initState();
    _myItems = // some sane default
    FirebaseDatabase.instance.reference().child("node")
      .then((items) 
        if (widget.mounted) setState(()  _myItems = items; 
      );


Widget build(BuildContext context) 
  return new Scaffold(
   ///start building your widget tree
  );

最好在这里使用FutureBuilder,但这是另一种方法(即使它不一定是最佳做法)。

【讨论】:

嗨,丹,在您的解决方案中,构建方法是否被调用了两次?第一次是 _myItems 为空,第二次是在调用 setState 之后?但是在重建期间,会再次调用 initState 吗? initState 仅在第一次将小部件添加到树时才被调用。但是是的,在这个例子中 build 会被调用两次,一次是在 state 的初始化期间,一次是在 future 解析之后。 initState 是不可取的,因为当你回到旧屏幕时,有时我们需要调用 API,为此,FutureBuilder 是最好的方法。

以上是关于Flutter:从 UI 调用异步代码的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

使用 fetch 在节点中进行异步 api 调用的最佳实践?

Flutter 又 7 个最佳实践

Flutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件

无论框架如何,在 UI 渲染后发布/订阅事件是最佳实践吗?

11 个 Flutter 最佳实践

Flutter 最佳实践