Flutter响应式编程 - Stream
1.前言
在Dart库中,有两种实现异步编程的方式(Future和Stream),使用它们只需要在代码中引入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的更多相关文章
- Flutter响应式编程 - RxDart
import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'dart:async'; cl ...
- 响应式编程(Reactive Programming)(Rx)介绍
很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...
- Java9第四篇-Reactive Stream API响应式编程
我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...
- Unity基于响应式编程(Reactive programming)入门
系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoT ...
- [译] Swift 的响应式编程
原文 https://github.com/bboyfeiyu/iOS-tech-frontier/blob/master/issue-3/Swift的响应式编程.md 原文链接 : Reactiv ...
- [iOS] 响应式编程开发-ReactiveCocoa(一)
什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...
- springboot2 webflux 响应式编程学习路径
springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑问将会成为未来技术的趋势,是必学 ...
- 响应式编程知多少 | Rx.NET 了解下
1. 引言 An API for asynchronous programming with observable streams. ReactiveX is a combination of the ...
- [转]springboot2 webflux 响应式编程学习路径
原文链接 spring官方文档 springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑 ...
随机推荐
- linux系统编程之管道(三)
今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...
- linux简单的命令
本文转自 https://www.cnblogs.com/pyyu/articles/9314206.html Linux命令行的组成结构 [root@oldboy_python ~]# [root@ ...
- redis cluster集群的原理
redis集群的概述: 在以前,如果前几年的时候,一般来说,redis如果要搞几个节点,每个节点存储一部分的数据,得借助一些中间件来实现,比如说有codis,或者twemproxy,都有.有一些red ...
- 《奋斗吧!菜鸟》 第九次作业:Beta冲刺 Scrum meeting 2
项目 内容 这个作业属于哪个课程 任课教师链接 作业要求 https://www.cnblogs.com/nwnu-daizh/p/11056511.html 团队名称 奋斗吧!菜鸟 作业学习目标 掌 ...
- 结构型模式(五) 外观模式(Facade)
一.动机(Motivate) 在软件系统开发的过程中,当组件的客户(即外部接口,或客户程序)和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战. ...
- 基于 Redis 实现简单的分布式锁
摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...
- 通过php安装Imagick扩展给动态gif图片打水印
通过php安装Imagick扩展给动态gif图片打水印 一直以来php处理图片都是以gd为主流,直到近些年Imagick的使用才渐渐变多. gd通常用来缩放图片,给图片打水印等基本功能,对于复杂效果如 ...
- python中的一切皆对象
python中一切皆对象是这个语言灵活的根本.函数和类也是对象,属于python的一等公民.包括代码包和模块也都是对象.python的面向对象更加彻底. 可以赋值给一个变量可以添加到集合对象中可以作为 ...
- Linux 常用命令1 pwd、ls、cd、tab、清屏、重定向、转义、管道、touch、mkdir、tree、cat、more、rmdir、rm、grep、help、man、history、find、cp、mv、tar、gz
版权声明:本文为博主引用文章,未经博主及作者允许不得转载. 声明: 涉及的命令:pwd.ls.cd.tab.清屏.重定向.转义.管道.touch.mkdir.tree.cat.more.rmdir. ...
- 014——C#新建文件夹
(一)如果不存在路径就新建文件夹 string directory = @"C:\Users\Administrator\Desktop\温控数据\"; if (!Director ...