RxDart學習筆記

參考了以下文章,感謝大俠們的無私付出:
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(),
              );
            }),
      ),
    );
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章