如何开始使用 Dart 和 Flutter 中的 Futures 和 Streams

介绍

通常,应用程序需要能够与外部 API 和数据库进行交互。随之而来的问题是,在等待某些请求和操作完成时,处理以与编写顺序不同的顺序运行的代码。

在本文中,您将探索Dart(尤其是Flutter)如何处理异步请求。

先决条件

要完成本教程,您需要:

本教程通过 Flutter v2.0.6、Android SDK v31.0.2、Android Studio v4.1 验证。

理解异步代码

使用同步代码,当我们向外部 API 发送信息请求时,需要一些时间才能得到响应。我们的机器将等待它完成,停止可能与初始请求无关的事情。问题是我们不希望我们的脚本在每次需要一段时间时停止运行,但我们也不希望它过早运行依赖于返回数据的任何东西,尽管请求成功,这可能会导致错误。

两全其美的方式是设置我们的逻辑,让我们的机器在等待请求返回时提前工作,而只让依赖于该请求的代码在可用时运行。

我们的应用程序的数据很可能是四种形式之一,具体取决于它们是否已经可用以及它们是否是单一的。

一个包含四个单元格的表格 - 两行两列。 有两个水平标签:第一个是可用的,第二个是等待中。 有两个垂直标签:第一个是 Singular,第二个是 Plural。 可用和复数需要列表/数组数据。 可用和奇异需要字符串数据。 Waiting 和 Singular 需要 Future 数据。 等待和复数需要流数据。

这个例子将探索 Futures 和 Streams。

设置项目

为 Flutter 设置环境后,您可以运行以下命令来创建新应用程序:

  • flutter create flutter_futures_example

导航到新的项目目录:

  • cd flutter_futures_example

使用flutter create将生成一个演示应用程序,该应用程序将显示单击按钮的次数。

此示例的一部分依赖于REST Country API如果您提供国家/地区名称,此 API 将返回有关国家/地区的信息。例如,这是一个请求Canada

https://restcountries.eu/rest/v2/name/Canada

这也需要http

pubspec.yaml在您的代码编辑器中打开并添加以下插件:

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  http: 0.13.3

现在,main.dart在您的代码编辑器中打开并修改以下代码行以显示“获取国家/地区”按钮:

lib/main.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  void getCountry() {}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: MaterialButton(
          onPressed: () => getCountry(),
          child: Container(
            color: Colors.blue,
            padding: EdgeInsets.all(15),
            child: Text('Get Country', style: TextStyle(color: Colors.white))
          ),
        ),
      ),
    );
  }
}

此时,您有了一个带有http的新 Flutter 项目

使用thencatchError

JavaScript 中的 try…catch非常相似,Dart 允许我们将方法链接在一起,这样我们就可以轻松地将返回数据从一个传递到另一个,它甚至返回一种类似于 Promise 的数据类型,称为 Futures。期货是任何单一类型的数据,如字符串,稍后可用。

要使用此技术,请执行您的操作,然后.then将我们作为参数传入的返回数据链接起来,然后根据需要使用它。此时,您可以继续链接其他.then. 对于错误处理,.catchError在最后使用 a并抛出传递给它的任何内容。

main.dart使用您的代码编辑器重新访问并使用.then.catchError首先,替换void GetCountry() {}Future GetCountry(country). 然后,将国家名称添加到onPressed: () => GetCountry()

lib/main.dart
// ...

class MyHomePage extends StatelessWidget {
  Future getCountry(country) {
    Uri countryUrl = Uri.http('restcountries.eu', '/rest/v2/name/$country');

    http
      .get(countryUrl)
      .then((response) => jsonDecode(response.body)[0]['name'])
      .then((decoded) => print(decoded))
      .catchError((error) => throw(error));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: MaterialButton(
          onPressed: () => getCountry('Canada'),
          child: Container(
            color: Colors.blue,
            padding: EdgeInsets.all(15),
            child: Text('Get Country', style: TextStyle(color: Colors.white))
          ),
        ),
      ),
    );
  }
}

保存更改并在模拟器中运行应用程序。然后,单击获取国家/地区按钮。您的控制台将记录国家/地区名称。

使用asyncawait

许多人认为可读性很强的另一种语法是 Async/Await。

Async/Await 的工作方式与JavaScript完全相同,我们async在函数名称之后使用关键字,并await在需要一些时间运行的任何内容之前添加关键字,例如我们的 get 请求。

main.dart使用您的代码编辑器重新访问并使用asyncawait现在,当返回一个值时,它之后的所有内容都将运行。对于错误处理,我们可以在 try/catch 块中抛出错误。

lib/main.dart
// ...

class MyHomePage extends StatelessWidget {
  Future getCountry(country) async {
    Uri countryUrl = Uri.http('restcountries.eu', '/rest/v2/name/$country');

    try {
      http.Response response = await http.get(countryUrl);
      Object decoded = jsonDecode(response.body)[0]['name'];
      print(decoded);
    } catch (e) { throw(e); }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: MaterialButton(
          onPressed: () => getCountry('Canada'),
          child: Container(
            color: Colors.blue,
            padding: EdgeInsets.all(15),
            child: Text('Get Country', style: TextStyle(color: Colors.white))
          ),
        ),
      ),
    );
  }
}

保存更改并在模拟器中运行应用程序。然后,单击获取国家/地区按钮。您的控制台将记录国家/地区名称。

使用流

Dart 的特别之处在于当我们有许多值被异步加载时,它会使用 Streams。不像我们的 GET 请求那样打开一次连接,我们可以让它保持打开状态并为新数据做好准备。

由于我们的示例通过设置允许 Streams 的后端而变得有点过于复杂,例如使用 Firebase或 GraphQL,我们将通过每秒发出一条新“消息”来模拟聊天应用程序数据库中的更改。

我们可以用这个StreamController创建一个 Stream ,它的工作方式类似于 a List,因为它的行为就像一个 Futures 列表。

我们可以使用 on stream、 likelisten属性来控制我们的 Streamclose来启动和停止它。

警告:close()当您的小部件被删除时,始终使用很重要流将持续运行,直到它们被关闭,即使原始小部件消失,也会消耗计算能力。

现在,main.dart在您的代码编辑器中打开并替换以下代码行以导入dart:async和使用StatefulWidget

lib/main.dart
import 'package:flutter/material.dart';
import 'dart:async';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  StreamController<String> streamController = StreamController();

  void newMessage(int number, String message) {
    final duration = Duration(seconds: number);

    Timer.periodic(duration, (Timer t) => streamController.add(message));
  }

  void initState() {
    super.initState();

    streamController.stream.listen((messages) => print(messages));

    newMessage(1, 'You got a message!');
  }

  void dispose() {
    streamController.close();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          padding: EdgeInsets.all(15),
          child: Text('Streams Example'),
        ),
      ),
    );
  }
}

此代码将You got a message在控制台中不断打印出来

标准流可能会受到一些限制,因为它们一次只允许一个侦听器。相反,我们可以使用broadcast上的StreamController属性来打开多个通道。

lib/main.dart
// ...

StreamController<String> streamController = StreamController.broadcast();

// ...

void initState() {
  super.initState();

  streamController.stream.listen((messages) => print('$messages - First'));
  streamController.stream.listen((messages) => print('$messages - Second'));

  newMessage(1, 'You got a message!');
}

// ...

此代码将连续打印出来You got a message - First,并You got a message - Second在控制台中。

结论

在本文中,您探索了 Dart(尤其是 Flutter)如何处理异步请求。Dart 中的异步编程将允许您开始开发智能和动态应用程序。

如果您想了解有关 Flutter 的更多信息,请查看我们的 Flutter 主题页面以获取练习和编程项目。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁