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

15

我正在开发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
    .....
    .....

如果我的描述不够清晰,非常欢迎提出建议。

Jimmy


理想情况下,有一个变量 isLoading = true,当为真时返回一个加载小部件。在 then 块中使用 setStateisLoading 设置为假。 - Hemanth Raj
@Hemanth 听起来不错! - Wonderjimmy
2个回答

29

在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 的状态。


2
我已经编辑了答案,包括StreamBuilder的示例和文档链接。 - Shady Aziza
非常感谢,我挣扎了几天,没有意识到Flutter有这么好的功能! - Wonderjimmy
@aziza:顺便说一下,我尝试了返回带有ProgressIndicator的Scaffold的FutureBuilder,看起来非常好,除了我想要空的Scaffold在确切的位置替换原始的Scaffold - 暂时交换现有的Scaffold,因此顶级小部件不会被隐藏。如何做到这一点? - Wonderjimmy
2
@Wonderjimmy ,这是我的失误,我应该表述更清楚。你绝不会想在 Scaffold 层级上使用 FutureBuilder,而是应该在 body 属性中使用。这样你的 appbar、drawer、FAB、tabs 等等都会正常加载,只有带有内容的主体部分会加载。 - Shady Aziza
嗯,除非我看到你处理这个问题的代码,否则我无法真正评判。然而,基本思路是你需要在不同的类中组合你的视图,你可以做类似于这样的事情 return user.hasData? new HomeScreen():new LoginScreen(),然后在你的登录屏幕中,你有触发按钮点击的登录方法,只有当该方法解决为所需结果时才将用户导航到HomeScreen(),否则处理错误/异常。 - Shady Aziza
显示剩余5条评论

3

虽然我认为@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,但这是另一种方法(即使不一定是最佳实践)。


嗨丹,你的解决方案中build方法被调用了两次吗?一次是当_myItems为空时,而第二次是在调用setState之后?但是在重建期间,initState会再次被调用吗? - Wonderjimmy
1
initState 只在小部件第一次添加到树中时调用。但是,在此示例中,build 将被调用两次,一次在状态初始化期间,一次在 future 解析后。 - Dan Field
initState 不是一个理想的选择,因为当你回到旧屏幕时,有时我们需要调用 API,对此,FutureBuilder 是最好的方法。 - Ankit ihelper Sharma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接