参考了以下文章,感谢大侠们的无私付出:
https://www.jianshu.com/p/612d43c8915f
https://juejin.im/post/5bcea438e51d4536c65d2232
https://www.jianshu.com/p/00060710a890
-
Subject实现并扩展了StreamController,它符合StreamController的所有规范。假如您之前使用的StreamController,那么你可以直接替换为Subject。你可以把它想像成streamController。
-
Observable实现并扩展了Stream。它将常用的stream和streamTransformer组合成了非常好用的api。你可以把它想像成stream。
创建Observables
- 从一个Stream中创建:
var controller = new StreamController<String>();
var streamObservable = new Observable(controller.stream);
streamObservable.listen(print);
- 通过Future创建:fromFuture
var obs = Observable.fromFuture(new Future.value("Hello"));
obs.listen(print);
- 通过Iterable创建:fromIterable
var obs = Observable.fromInterable([1,2,3,4,5]);
obs.listen(print);
- 从单个值中创建
var justObservable = Observable<int>.just(42);
justObservable.listen(print);
- 其他骚操作
基本与RxJava一致,只是subscribe方法换成了listen,取消变成了StreamSubscription 。 - 从Future中创建
通过一个Future创建的Observable,会先等待Future执行完毕,完后发射数据,这个输出数据就是Future的执行结果,如果Future没有任何返回值,那么输出null。另一种从Future中创建Stream的方法是调用Future的toStream()方法。
Future<String> asyncFunction() async {
return Future.delayed(const Duration(seconds: 1),
() => "AsyncRsult");
}
test('Create Observable from Future', () async {
print('start');
var fromFutureObservable = Observable.fromFuture(asyncFunction());
fromFutureObservable.listen(print);
}
Subjects
Subjects是RxDart的流控制器(StreamController),但Subjects但行为跟StreamControllers还是有些区别的:
- 你可以在一个Subject上直接listen(),而不需要拥有对这个Stream的访问权限;
- 你可以添加多个subscription,它们会同时收到同样的数据;
Subjects有三种类型:
PublishSubjects
和StreamControllers的行为很像,也支持多个监听,默认是sync是false,也就是说里面是一个AsyncBroadcastStreamController 异步广播流:
var subject = new PublishSubject<String>();
subject.listen((item) => print(item));
subject.add("Item1");
// 添加第二个listener
subject.listen((item) => print(item.toUpperCase()));
subject.add("Item2");
subject.add("Item3");
subject.close();
输出结果:
因为第二个监听是在中途加进来的,所以它并没有监听到数据Item1。
BehaviourSubject
每一个新加的监听,接收到的第一个数据都是上一个数据(再往前的数据不会监听到,只会缓存一个数据)。
var subject = new BehaviorSubject<String>();
subject.listen((item) => print(item));
subject.add("Item1");
subject.add("Item2");
subject.listen((item) => print(item.toUpperCase()));
subject.add("Item3");
subject.close();
我们发现第二个subscriber没有监听到Item1,但是监听到了Item2,**而且第二个subscriber比第一个subscriber先监听到了Item3。**这是因为,你没法决定多个监听的服务顺序(实际上对于单个item,总是后加的监听先接收到数据),但是每个监听获取到的数据依然是有序的。BehaviourSubject只会为后加的Subscribers缓存最近的一条输出数据。
总结:每一个新加的监听接收到的第一条数据,是最近的那条数据(也就是只会缓存最近一条数据的意思);对于单条数据而言,总是后加的监听先接收到。
也可以添加默认接收数据的值seeded:
如果你想要缓存更多的数据,可以使用ReplaySubject,但是大多数情况下我们都用不到。
ReplaySubject
回放已经消失的事件。
final subject1 = ReplaySubject<int>();
subject1.add(1);
subject1.add(2);
subject1.add(3);
subject1.stream.listen(print); // prints 1, 2, 3
subject1.stream.listen(print); // prints 1, 2, 3
subject1.stream.listen(print); // prints 1, 2, 3
final subject2 = ReplaySubject<int>(maxSize: 2);
subject2.add(1);
subject2.add(2);
subject2.add(3);
subject2.stream.listen(print); // prints 2, 3
subject2.stream.listen(print); // prints 2, 3
subject2.stream.listen(print); // prints 2, 3
常用的数据变换
map
与RxJava一样,不再赘述。
除了Streams之外,每一个Iterable都提供了map方法,将其转化为一个List。
创建周期性事件
periodic重复性操作
var timerObservable = Observable.periodic(Duration(seconds: 1),
(x) => x.toString() );//函数可以不写,x是事件的序列号,从0开始
timerObservable.listen(print);
完整的代码如下:
StreamSubscription subs;
subs = Observable.periodic(Duration(seconds: 10), (index) {
return "primitive $index";
}).map((value) => "ss $value").listen((data) {
print("data is $data");
if (data.contains("8")) {
subs.cancel();
}
});
interval实现上面的逻辑
var obs = Observable(Stream.fromIterable([1,2,3,4,5]))
.interval(new Duration(seconds: 1));
obs.listen(print);
输出:1 … 2 … 3 … 4 … 5,其中…代表停顿了一秒。
延时操作
Observable.timer("Hello Timer", Duration(seconds: 20)).listen((data) {
print("data is $data");
});
concat
与RxJava一致,不再赘述。
Where:数据过滤
如果你只关心Stream中的特定数据,那么可以使用.where()函数。这个函数其实就是替代if语句的,但是.where()会更加方便阅读:
var subject = new PublishSubject<int>();
subject.where((val) => val.isOdd)
.listen((val) => print('This only prints odd numbers: $val'));
subject.where((val) => val.isEven)
.listen((val) => print('This only prints even numbers: $val'));
subject.add(1);
subject.add(2);
subject.add(3);
subject.close();
Debounce:数据拦截
与RxJava一致,不再赘述。
Expand:展开数组
var list = [1, 8, 9, 20, 36, 68, 99];
var subject = Observable.fromIterable(list);
subject.expand((data) {
//对应也可以是[data,"number is $data"]表示添加元素
return ["number is $data"];
}).listen((data) {
print(data);
});
乍一看和map差不多,但它更加强大,可以添加元素,修改元素。
mergeWith
即RxJava的merge。
Distinct:过滤相同数据
同RxJava,不再赘述。
ZipWith:数据合并
zipWith也是将一个Stream和另一个合并到一起,但是它和.mergeWith不一样。.mergeWith只要拿到了数据就立刻发射出去,但是zipWith会等到所有数据都接收完毕后,将这些数据重组后再发射出去。与RxJava一致,不再赘述。
CombineLatest:组合数据
combineLatest跟merge和zip一样也是组合数据,但是有一些轻微都区别。它接收到一个Stream的数据后,不仅仅会发射这个Stream带来的数据,还会将其他Stream中的最近的数据也发射出去。也就是说,有n个Stream,它每一次就发射n个数据,发射的数据是每个Stream上最近的一条数据;任意一个Stream的数据进来的时候,都会触发一次发射;刚开始的时候,数据种类不足n时,会等待(也就是第一次发射必须保证每个Stream都有数据被传递过来),与RxJava一致。
检查每一个item:every
every会检查每个item是否符合要求,然后它将会返回一个能够被转化为 Observable 的 AsObservableFuture。
var obs = Observable.fromIterable([1,2,3,4,5]);
obs.every((x)=> x < 10).asObservable().listen(print);
输出结果:true
AsyncMap:异步数据转换
除了map(),还有一个asyncMap方法,让你可以在map函数中进行异步操作。
Observable<CombinedMessage> getDependendMessages() {
Observable<NewsMessage> news = newsCollection.snapshots().expand((snapShot) {
return snapShot.documents;
}).map<NewsMessage>((document) {
return NewsMessage.fromMap(document.data);
});
return news.asyncMap((newsEntry) async {
var weatherDocuments =
await weatherCollection.where('location', isEqualTo: newsEntry.location).getDocuments();
return new CombinedMessage(
WeatherForecast.fromMap(weatherDocuments.documents.first.data), newsEntry);
});
}
类似RxJava的切换线程。
需要注意的地方
RxDart的transforming函数应该只用来处理数据流,所以不要尝试在这些函数中修改变量/状态(variables/state),这些代码请写在.listen中。所以,不要这么写:
Observable.fromFuture(getProduct())
.map<Product>((jsonString) {
var product = Product.fromJson(jsonString);
database.save(product);
setState((){ _product = product });
return product;
}).listen();
而是这么写:
Observable.fromFuture(getProduct())
.map<Product>((jsonString) => Product.fromJson(jsonString))
.listen( (product) {
database.save(product);
setState((){ _product = product });
});
map()函数的唯一作用就是数据转换,不要在里面做任何多余的操作。在map函数里面写其他操作也会降低代码的可读性,也容易隐藏一些bug。
tips
-
为了防止内存泄漏,请在适当的时候调用subscriptions的cancel()方法,或者者dispose掉你的StreamControllers,或者关闭你的Subjects。
-
Dart中 Observables 默认是单一订阅。如果您尝试两次收听它,则会抛出 StateError 。你可以使用工厂方法或者 asBroadcastStream 将其转化为多订阅流。
var obs = Observable(Stream.fromIterable([1,2,3,4,5])).asBroadcastStream();
-
很多方法的返回值并不是一个 Single 也不是一个 Observable 而是必须返回一个Dart的 Future。幸运的是你很容易找到一些方法,把他们转化成回 stream。
-
出现错误时,Dart中的Stream不会默认关闭。但是在Rxdart中,Error会导致Observable终止,除非它被运算符拦截。
-
默认情况下Dart中Stream是异步的,而Observables默认是同步的。
-
在处理多订阅Observable的时候,onListen方法只有在第一次会被调用。且各个订阅者之间不会互相干涉。
var obs = Observable(Stream.fromIterable([1,2,3,4,5])).asBroadcastStream();
//第一个订阅者
obs.interval(Duration(seconds: 1)).map((item) => ++item).listen(print);
//第二个订阅者
obs.listen(print);
输出:1 2 3 4 5 2 3 4 5 6
应用
生成重复的view
var subject = ReplaySubject<List<String>>();
subject.add(['a', 'b', 'c', 'd']);
return Scaffold(
appBar: AppBar(
title: Text("这是根据StreamBuilder生成的view tree"),
),
body: Center(
child: StreamBuilder(
stream: subject.stream,
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
subject.close();
return Column(
children: snapshot.data.map((text) {
return Text(
text,
style: TextStyle(fontSize: 30),
);
}).toList(),
);
}),
),
);