參考了以下文章,感謝大俠們的無私付出:
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(),
);
}),
),
);