首页 扑腾书籍 扑腾学徒

15
溪流 由凯文摩尔撰写

想象一下,自己坐在一条小溪,度过美好的时光。在观看水流的同时,你看到一块木头或漂浮在溪流的叶子,你决定把它从水中取出。你甚至可以让某人上游故意浮动的东西,让你抓住。

您可以以类似的方式想象Dart Streams:随着数据流下溪的数据,等待某人抓住它。这就是DART中的流 - 它为侦听器发送了数据事件。

使用Dart Streams,您可以一次发送一个数据事件,而应用程序的其他部分侦听这些事件。此类事件可以是您创建的集合,映射或任何其他类型的数据。

除数据外,流可以发送错误;如果需要,您也可以停止流。

在本章中,您将更新您的配方项目以在两个不同位置使用流。您将使用一个用于书签,让用户标记喜爱的食谱并自动更新UI以显示它们。您将使用第二个以更新您的成分和杂货名单。

但在跳入代码之前,您将更多地了解流程如何工作。

流的类型

溪流是Dart的一部分,摇动“继承”它们。扑动中有两种类型的流:单个订阅流和广播流。

单个订阅流 是默认值。当您仅在一个屏幕上使用特定的小型时,它们运行良好。

单个订阅流只能忽略一次。它不会开始生成事件,直到它具有侦听器,即使事件源仍然可以提供更多数据,它也停止发送事件。

单个订阅流可用于下载文件或任何单一使用操作。例如,窗口小部件可以订阅流以接收关于值的更新,例如下载的进度,并相应地更新其UI。

如果您需要应用的多个部分来访问相同的流,请使用广播流。

A 广播流 允许任何数量的侦听器。它在其活动准备好时触发,是否有侦听器。

To create a broadcast stream, you simply call asBroadcastStream() on an existing single subscription stream.

final broadcastStream =  singleStream.asBroadcastStream();

You can differentiate a broadcast stream from a single subscription stream by inspecting its Boolean property isBroadcast.

In Flutter, there are some key classes built on top of Stream that simplify programming with streams.

下图显示了具有流的主要类:

接下来,你会越来越深看一下。

StreamController和汇

When you create a stream, you usually use StreamController, which holds both the stream and StreamSink. Here’s an example that uses StreamController:

final _recipeStreamController = StreamController<List<Recipe>>();
final _stream = _recipeStreamController.stream;
_recipeStreamController.sink.add(_recipesList);
_recipeStreamController.close();

StreamSubscription.

Using listen() on a stream returns a StreamSubscription.. You can use this subscription class to cancel the stream when you’re done, like this:

StreamSubscription. s = stream.listen((value) {
    print('Value from controller: $value');
});
...
...
// You are done with the subscription
subscription.cancel();

streambuilder.

streambuilder. 当您想使用流时,很方便。它需要两个参数:流和构建器。当您从Stream收到数据时,Builder负责构建或更新UI。

final repository = Provider.of<Repository>(context, listen: false);
  return StreamBuilder<List<Recipe>>(
    stream: repository.recipesStream(),
    builder: (context, AsyncSnapshot<List<Recipe>> snapshot) {
      // extract recipes from snapshot and build the view
    }
  )
...

将流添加到配方查找器

你现在准备好开始在你的食谱项目上工作。如果您与上一章中的应用一起关注您的应用程序,请打开它并继续使用本章使用它。如果没有,请点击 项目 本章文件夹和打开 起动机 in Android Studio.

将期货和流添加到存储库

打开 数据/ repository.dart.dart. and change all of the return types to return a Future. For example, change the existing findAllRecipes() to:

Future<List<Recipe>> findAllRecipes();
Future<List<Recipe>> findAllRecipes();

Future<Recipe> findRecipeById(int id);

Future<List<Ingredient>> findAllIngredients();

Future<List<Ingredient>> findRecipeIngredients(int recipeId);

Future<int> insertRecipe(Recipe recipe);

Future<List<int>> insertIngredients(List<Ingredient> ingredients);

Future<void> deleteRecipe(Recipe recipe);

Future<void> deleteIngredient(Ingredient ingredient);

Future<void> deleteIngredients(List<Ingredient> ingredients);

Future<void> deleteRecipeIngredients(int recipeId);

Future init();

void close();
// 1
Stream<List<Recipe>> watchAllRecipes();
// 2
Stream<List<Ingredient>> watchAllIngredients();

清理存储库代码

在更新使用流和期货的代码之前,还有一些小型房间更新。

import 'dart:async';
class MemoryRepository extends Repository {
//1
Stream<List<Recipe>> _recipeStream;
Stream<List<Ingredient>> _ingredientStream;
// 2
final StreamController _recipeStreamController =
    StreamController<List<Recipe>>();
final StreamController _ingredientStreamController =
    StreamController<List<Ingredient>>();
// 3
@override
Stream<List<Recipe>> watchAllRecipes() {
  if (_recipeStream == null) {
    _recipeStream = _recipeStreamController.stream;
  }
  return _recipeStream;
}

// 4
@override
Stream<List<Ingredient>> watchAllIngredients() {
  if (_ingredientStream == null) {
    _ingredientStream = _ingredientStreamController.stream;
  }
  return _ingredientStream;
}

更新现有存储库

MemoryRepository is full of red squiggles. That’s because the methods all use the old signatures, and everything’s now based on Futures.

@override
// 1
Future<List<Recipe>> findAllRecipes() {
  // 2
  return Future.value(_currentRecipes);
}
@override
Future init() {
  return Future.value();
}
@override
void close() {
  _recipeStreamController.close();
  _ingredientStreamController.close();
}

在流中发送食谱

As you learned earlier, StreamController’s sink property adds data to streams. Since this happens in the future, you need to change the return type to Future and then update the methods to add data to the stream.

@override
// 1
Future<int> insertRecipe(Recipe recipe) {
  _currentRecipes.add(recipe);
  // 2
  _recipeStreamController.sink.add(_currentRecipes);
  insertIngredients(recipe.ingredients);
  // 3
  // 4
  return Future.value(0);
}

挑战

Convert the remaining methods, just like you just did with insertRecipe(). You’ll need to do the following:

return Future.value();

在服务之间切换

在上一章中,您创建了一个 MockService. 提供永不改变的本地数据,但也可以访问 Concoreservice.。在两者之间切换仍然有点繁琐,因此在集成流之前,您将在绘制之前照顾。

import 'package:chopper/chopper.dart';
import 'model_response.dart';
import 'recipe_model.dart';
abstract class ServiceInterface {
  Future<Response<Result<APIRecipeQuery>>> queryRecipes(
      String query, int from, int to);
}

实现新的服务界面

打开 网络/ refipe_service.dart. 并添加 service_Interface. import:

import 'service_interface.dart';
abstract class RecipeService extends ChopperService
    implements ServiceInterface {
import '../network/service_interface.dart';
class MockService implements ServiceInterface {

改变提供者

现在,您将采用新的服务界面而不是当前代码中使用的特定服务。

import 'data/repository.dart';
import '网络/ refipe_service.dart.';
import 'network/service_interface.dart';
Provider<Repository>(
  lazy: false,
  create: (_) => MemoryRepository(),
),
Provider<ServiceInterface>(
  create: (_) => RecipeService.create(),
  lazy: false,
),
import '../../data/repository.dart';
final repository = Provider.of<Repository>(context);
import '../../network/service_interface.dart';
future: Provider.of<ServiceInterface>(context).queryRecipes(

将流添加到书签中

The Bookmarks page uses Consumer, but you want to change it to a stream so it can react when a user bookmarks a recipe. To do this, you need to replace the reference to MemoryRepository with Repository and use a streambuilder. widget.

import '../../data/repository.dart';
// 1
final repository = Provider.of<Repository>(context, listen: false);
// 2
return StreamBuilder<List<Recipe>>(
  // 3
  stream: repository.watchAllRecipes(),
  // 4
  builder: (context, AsyncSnapshot<List<Recipe>> snapshot) {
    // 5
    if (snapshot.connectionState == ConnectionState.active) {
      // 6
      final recipes = snapshot.data ?? List();
} else {
  return Container();
}
void deleteRecipe(Repository repository, Recipe recipe) async {

向杂货添加流

首先开放 UI /购物/ shopping_list.dart 并更换 memory_repository.dart. import with:

import '../../data/repository.dart';
final repository = Provider.of<Repository>(context);
return StreamBuilder(
  stream: repository.watchAllIngredients(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      final ingredients = snapshot.data;
      if (ingredients == null) {
        return Container();
      }
} else {
  return Container();
}

关键点

  • 溪流是一种异步将数据发送到应用程序的其他部分的方法。
  • You usually create streams by using StreamController.
  • Use streambuilder. to add a stream to your UI.
  • 抽象类或界面,是抽象功能的好方法。

然后去哪儿?

在本章中,您学习了如何使用流。如果您想了解有关该主题的更多信息,请访问Dart文档 //dart.dev/tutorials/language/streams.

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。