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可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑 ...
随机推荐
- Java精通并发-同步方法访问标志与synchronized关键字之间的关系
继续基于上一次https://www.cnblogs.com/webor2006/p/11428811.html来研究synchronized关键字在字节码中的表现,在上一次文末提出了一个这样的问题: ...
- 第三天Beta冲刺
团队作业Beta冲刺 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 你们都是魔鬼吗 作业学习目标 (1)掌握软件黑盒测试技术:(2)学会编制软件 ...
- pycharm 远程修改服务器代码
首先在本地和服务器上下载pydevd pip3 install pydevd 然后在 设置SSH连接, 出现:java.net.ConnectException:Connection refused ...
- 2019-2020-1 20199312 《Linux内核原理与分析》 第九周作业
进程调度 1.中断:起到切出进程指令流的作用.中断处理程序是与进程无关的内核指令流.中断类型: 硬中断:可屏蔽中断和不可屏蔽中断.高电平说明有中断请求. 软中断/异常: 故障:出问题,但可以恢复到当前 ...
- CF1106F Lunar New Year and a Recursive Sequence——矩阵快速幂&&bsgs
题意 设 $$f_i = \left\{\begin{matrix}1 , \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i < k\\ ...
- Java - 框架之 Struts2
一. Package 标签 1. 标签属性:name : 包的名称,只有在一个项目中不重名即可.extends : 继承哪个包,默认为 struts-default.name ...
- 0.0.Pycharm使用技巧
调整自动字体大小 Increase(字体变大) Decrease(字体变小) 背景颜色设置 pycharm 左侧菜单问题 解决 pycharm中配置启动Django项目 1.先打开mange.py,然 ...
- Java【基础学习】之暴力求素数【用数组返回】
Java[基础学习]之暴力求素数[用数组返回] */ import java.util.*; public class Main{ public static void main(String[] a ...
- Oracle 分区表中本地索引和全局索引的适用场景
背景 分区表创建好了之后,如果需要最大化分区表的性能就需要结合索引的使用,分区表有两种索引:本地索引和全局索引.既然存在着两种的索引类型,相信存在即合理.既然存在就会有存在的原因,也就是在特定的场景中 ...
- learning java 访问文件和目录
import java.io.File; import java.io.IOException; public class FileTest { public static void main(Str ...