1.前言

在Dart库中,有两种实现异步编程的方式(FutureStream),使用它们只需要在代码中引入dart:async即可。
本文主要介绍Stream的相关概念及利用其异步特性来实现简单的响应式编程。

2.什么是Stream?

为了将Stream的概念可视化与简单化,可以将它想成是管道(pipe)的两端,它只允许从一端插入数据并通过管道从另外一端流出数据。
在Flutter中,

  • 我们将这样的管道称作Stream
  • 为了控制Stream,我们通常可以使用StreamController来进行管理;
  • 为了向Stream中插入数据,StreamController提供了类型为StreamSink的属性sink作为入口;
  • StreamController提供stream属性作为数据的出口。

通常在本文范围内我们会使用StreamController来管理Stream,后续文章在引入rxdart这个库之后会更多的使用Subject。

3.Stream可以传输什么?

任何东西都可以!包括简单的值,事件,对象,集合,map,error或者其他的Stream,任何类型的数据都可以使用Stream来传输。

4.如何感知Stream中传输的数据?

当你需要使用Stream中传输的数据时,可以简单地使用listen函数来监听StreamController的stream属性。
在定义完listener(监听者)之后,我们会收到StreamSubscription(订阅)对象,通过这个订阅对象我们就可以接收到Stream发送数据变更的通知。
只要至少有一个活跃的监听者,Stream会创建以下事件来通知订阅对象:

  • 数据从Stream中流出;
  • Stream接收到错误信息;
  • Stream关闭。

同时,你也可以通过订阅对象来:

  • 停止监听;
  • 暂停监听;
  • 恢复监听。

5.Stream是单纯的管道?

肯定不是!Stream均可以在数据流入之前和流出之前对数据进行处理。
我们可以使用StreamTransformer来处理Stream中的数据,它具有以下特点:

  • 它是Stream中能够捕获数据流的一系列函数;
  • 可以对Stream中的数据进行处理加工;
  • 经过处理的Stream依然是Stream(链式编码的前提)。

StreamTransformer可以用于以下场景(包括但不仅限于):

  • 过滤:过滤基于条件的任何类型数据;
  • 修改:对任何类型数据进行修改;
  • 重新分组:对数据进行重新分组;
  • 向其他Stream注入数据;
  • 缓存
  • 其他基于数据的行为和操作;
  • ...

6.Stream的类型

有两种类型的Stream。

单点订阅(单播)Stream

该类型的Stream在其整个生命周期中只有一个监听者。

如果订阅已经被取消,就不能再次监听这个Stream。

多点订阅(广播)Stream

对比前者,该类型的Stream不限制监听者的数量。

我们可以在广播Stream的任何周期添加监听者,新的监听者会在监听的同时收到相应的订阅。

7.如何使用Stream流出数据构建Widget?

Flutter提供了名为StreamBuilder的类,它监听Stream,并且在Stream数据流出时会自动重新构建widget,通过其builder进行回调。
以下是示例代码:

StreamBuilder<T>(key: <#该组件的唯一ID,可选#>, stream: <#被监听的Stream#>, initialData: <#初始数据,可选#>, builder:(BuildContext context, AsyncSnapshot<T> snapshot) {
return <#实际的widget构造代码#>;
})

demo

import 'dart:async';

import 'package:flutter/material.dart';

class StreamDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('StreamDemo'),
elevation: 0.0,
),
body: StreamDemoHome(),
);
}
} class StreamDemoHome extends StatefulWidget {
@override
_StreamDemoHomeState createState() => _StreamDemoHomeState();
} class _StreamDemoHomeState extends State<StreamDemoHome> {
StreamSubscription _streamDemoSubscription;
StreamController<String> _streamDemo;
StreamSink _sinkDemo;
String _data = '...'; @override
void dispose() {
_streamDemo.close();
super.dispose();
} @override
void initState() {
super.initState(); print('Create a stream.');
// Stream<String> _streamDemo = Stream.fromFuture(fetchData());
_streamDemo = StreamController.broadcast();
_sinkDemo = _streamDemo.sink; print('Start listening on a stream.');
_streamDemoSubscription =
_streamDemo.stream.listen(onData, onError: onError, onDone: onDone); _streamDemo.stream.listen(onDataTwo, onError: onError, onDone: onDone); print('Initialize completed.');
} void onDone() {
print('Done!');
} void onError(error) {
print('Error: $error');
} void onData(String data) {
setState(() {
_data = data;
});
print('$data');
} void onDataTwo(String data) {
print('onDataTwo: $data');
} void _pauseStream() {
print('Pause subscription');
_streamDemoSubscription.pause();
} void _resumeStream() {
print('Resume subscription');
_streamDemoSubscription.resume();
} void _cancelStream() {
print('Cancel subscription');
_streamDemoSubscription.cancel();
} void _addDataToStream() async {
print('Add data to stream.'); String data = await fetchData();
// _streamDemo.add(data);
_sinkDemo.add(data);
} Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 5));
// throw 'Something happened';
return 'hello ~';
} @override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Text(_data),
StreamBuilder(
stream: _streamDemo.stream,
initialData: '...',
builder: (context, snapshot) {
return Text('${snapshot.data}');
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text('Add'),
onPressed: _addDataToStream,
),
FlatButton(
child: Text('Pause'),
onPressed: _pauseStream,
),
FlatButton(
child: Text('Resume'),
onPressed: _resumeStream,
),
FlatButton(
child: Text('Cancel'),
onPressed: _cancelStream,
),
],
),
],
),
),
);
}
}

效果:

Flutter响应式编程 - Stream的更多相关文章

  1. Flutter响应式编程 - RxDart

    import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'dart:async'; cl ...

  2. 响应式编程(Reactive Programming)(Rx)介绍

    很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...

  3. Java9第四篇-Reactive Stream API响应式编程

    我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...

  4. Unity基于响应式编程(Reactive programming)入门

    系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...

  5. [译] Swift 的响应式编程

    原文  https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-3/Swift的响应式编程.md 原文链接 : Reactiv ...

  6. [iOS] 响应式编程开发-ReactiveCocoa(一)

    什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...

  7. springboot2 webflux 响应式编程学习路径

    springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑问将会成为未来技术的趋势,是必学 ...

  8. 响应式编程知多少 | Rx.NET 了解下

    1. 引言 An API for asynchronous programming with observable streams. ReactiveX is a combination of the ...

  9. [转]springboot2 webflux 响应式编程学习路径

    原文链接 spring官方文档 springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑 ...

随机推荐

  1. 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()

    java编程里有关约定:如果两个对象根据equals方法比较是相等的,那么调用这两个对象的任意一个hashcode方法都必须产生相同的结果. hashcode()和equals()都继承于object ...

  2. abp学习(一)

    官网概念 ASP.NET样板是特别设计的新的现代Web应用程序的通用应用框架.它使用已经熟悉的工具并围绕这些工具实现最佳实践,从而为您提供一致的开发体验. 官网地址:https://aspnetboi ...

  3. 钉钉、阿里云和PaaS平台的整合开发

    钉钉在企业移动办公领域有着很高的占有率,但是可能大家都会觉得,他在企业定制化,数据分析等领域有着很大的短板. 而我们的kintone作为PaaS平台,可以补足这个短板.很多开发者想知道如何利用钉钉还有 ...

  4. LGOJP2831 愤怒的小鸟

    题目链接 题目链接 题解 数据范围显然状压/爆搜. 考虑\(f[S]\)表示二进制下已打了的猪的集合. 可以枚举\(S\)的子集\(S_1\),判定\(S\)中\(S_1\)的补集\(S_2\)是否合 ...

  5. 创建型模式(二) 工厂方法模式(Factory Method)

    一.动机(Motivation) 在软件系统创建过程中,经常面临着"某个对象"的创建工作:由于需求的变化,这个对象(的具体实现)经常面临着剧烈的变化,但是它却拥有比较稳定的接口.如 ...

  6. Exec Maven插件

    1.为什么使用exec? 现在的工程往往依赖 众多的jar包,不像war包工程,对于那些打包成jar包形式的本地java应用来说,通过java命令启动将会是一件极为繁琐的事情,原因很简单,太 多的依赖 ...

  7. Eclipse下,Maven+JRebel安装破解手记

    Java开发中,Maven已经是标配,使用JRebel能大大地提高工作效率,特别是在Web开发中,不用重启tomcat,大大地提高了工作效率. 1.前提条件 安装JDK 8 安装eclipse, ec ...

  8. Python通过logging记录日志并应用coloredlogs在控制台输出有色字体

    import logging import os from logging.handlers import TimedRotatingFileHandler import coloredlogs # ...

  9. 使用fastjson 进行jsonObject转实体类对象

    使用fastjson 进行jsonObject转实体类对象   1 <dependency> 2 <groupId>com.alibaba</groupId> 3 ...

  10. identifier of an instance of com.xxx.model.system.xxxObject was altered from 1765 to 1766

    Caused by: org.hibernate.HibernateException: identifier of an instance of com.xxx.model.system.xxxOb ...