RxDartのBehaviorSubjectとPublishSubjectの違いと使い分け

## 前回までの記事
このシリーズではFlutterにおけるBLoCパターンとその周辺知識について解説しています。
この記事は以下の記事の続きとなりますが、この記事から読み始めてもOKです。

Stream/RxDart初心者のためのBLoC入門
https://qiita.com/tetsufe/items/521014ddc59f8d1df581

Stream/Sinkを使いこなす！ Stream/RxDart初心者のためのBLoC入門 part2
https://qiita.com/tetsufe/items/7b2f8592f5161104d1cd


今回はRxDart編です。といってもまだRxについての知識が少ないため、非常に短い内容です。

## BehaviorSubjectとPublishSubjectの使い分け 
使い分けは基本的に以下を基準にすれば良いです。

- BehaviorSubject
    - 初期値を **使う** ならコレ
- PublishSubject
    - 初期値を **使わない** ならコレ


## BehaviorSubjectの特徴
基本的にStreamControllerと同じように使えますが、broadcast(hot)です。

BehaviorSubject.value プロパティ（メンバ）により、最後に送られてきた値に同期的にアクセスできます。また、StreamControllerとは異なり初期値を設定できるため、StreamBuilderを使う場合にinitialValueを適切にセットでき、画面のチラツキを防止できます。

さらに詳しく知りたい方はこちらをどうぞ。
https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html


## PublishSubjectの特徴
初期値を持たないBehaviorSubject。valueプロパティもありません。broadcast(hot)です。

さらに詳しく知りたい方はこちらをどうぞ。
https://pub.dev/documentation/rxdart/latest/rx/PublishSubject-class.html


## よくあるBlocでの使い方
Blocでの使い方としては、カウンターアプリを例にするとわかりやすいです。

今回もpart1と同じくカウンターアプリを例に説明します。右下のFloatingActionButtonを押すとカウントアップされるというシンプルなものです。

<img width="300" alt="Simulator Screen Shot - iPhone 8 - 2019-07-24 at 21.41.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164020/c2b1da55-df82-50ef-e2e9-0c52265867c1.png">

### ライブラリ

```pubspec.yaml
dependencies:
  # 省略
  provider: 3.0.0+1
  rxdart: 0.22.1
```

### BLoC

```counter_bloc.dart
import 'package:rxdart/rxdart.dart';

class CounterBloc {
  final _actionController = PublishSubject<void>();
  Sink<void> get increment => _actionController.sink;

  final _countController = BehaviorSubject<int>.seeded(0);
  ValueObservable<int> get count => _countController;

  int _count = 0;

  CounterBloc() {
    _actionController.stream.listen((_) {
      _count++;
      _countController.sink.add(_count);
    });
  }

  void dispose() {
    _actionController.close();
    _countController.close();
  }
}
```

コードを見るとわかるように、PublishSubjectとBehaviorSubjectの２つのSubjectを使い分けています。

まず一つの理由としては、アクション（今回はカウントアップ用ボタンのタップ）に関するSubject(StreamController)とカウンターの値に関するSubjectを分けることで、アクションとロジックを分離することができるためです。

二つ目の理由としては、**アクションに関するSubject側では初期値が必要なくPublishSubjectが適しており、カウンターの値に関するSubject側では初期値が必要なため、BehaviorSubjectを用いています。**

アプリの流れとしては、

1. コンストラクタで actionController の stream に値が流れてきたときのリスナーを設定
2. ボタンのタップ時に increments.add(null) でタップされたことをBlocに通知
3. 2 をトリガーとして 1 で設定したロジックにより count を更新
4. count を StreamBuilder で表示

### Widget
以下にWidget側を示します。 main.dart は、ほぼFlutterのデフォルトカウンターアプリです。

```main.dart
import 'package:counter_bloc/counter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Provider<CounterBloc>(
          builder: (context) => CounterBloc(),
          dispose: (context, bloc) => bloc.dispose(),
          child: MyHomePage(title: 'Flutter Demo Home Page'),
        ));
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    final counterBloc = Provider.of<CounterBloc>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder(
              initialData: counterBloc.count.value,
              stream: counterBloc.count,
              builder: (context, snapshot) {
                return Text(
                  '${snapshot.data}',
                  style: Theme.of(context).textTheme.display1,
                );
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counterBloc.increment.add(null);
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
```


BehavitorSubjectとStreamBuilderを一緒に利用する場合は、以下のようにvalueプロパティを活用しましょう。これにより一時的にnullが入って画面がちらつくことを防ぐことができます。

```dart
StreamBuilder(
    initialData: counterBloc.count.value,
    stream: counterBloc.count,
    builder: (context, snapshot) {
        return Text(
            '${snapshot.data}',
             style: Theme.of(context).textTheme.display1,
        );
    },
)
```


また、RxDartでは、`scan`と`pipe`を使うことで、以下のように書くこともできます。（[pipeはDart標準の Stream APIにもあります](https://api.flutter.dev/flutter/dart-async/Stream/pipe.html)）


```counter_bloc.dart
import 'package:rxdart/rxdart.dart';

class CounterBloc {
  final _actionController = PublishSubject<void>();
  Sink<void> get increment => _actionController.sink;

  final _countController = BehaviorSubject<int>.seeded(0);
  ValueObservable<int> get count => _countController;

  CounterBloc() {
    _actionController
        .scan<int>((s, v, i) => s + 1, 0)
        .pipe(_countController);
  }

  void dispose() {
    _actionController.close();
    _countController.close();
  }
}
```


## まとめ
BehaviorSubjectとPublishSubjectの使い分けは、基本的に以下を基準にすれば良いです。

- BehaviorSubject
    - 初期値を **使う** ならコレ
- PublishSubject
    - 初期値を **使わない** ならコレ

また、StreamBuilderでBehaviorSubjectを使う場合は、valueプロパティを活用しましょう。


## 前回までの記事
このシリーズではFlutterにおけるBLoCパターンとその周辺知識について解説しています。よろしければ以下もどうぞ。

Stream/RxDart初心者のためのBLoC入門
https://qiita.com/tetsufe/items/521014ddc59f8d1df581

Stream/Sinkを使いこなす！ Stream/RxDart初心者のためのBLoC入門 part2
https://qiita.com/tetsufe/items/7b2f8592f5161104d1cd


## 次の記事
FlutterでBLoCの値の変化に応じて処理を行う（モーダル表示）
https://qiita.com/tetsufe/items/6529c20d5f8f4a3b4bc0#_reference-814007adf85232c717a8


## 参考
BehaviorSubject<T> class
https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html

PublishSubject<T> class
https://pub.dev/documentation/rxdart/latest/rx/PublishSubject-class.html

bloc_provider パッケージのサンプルコード
https://github.com/mono0926/bloc_provider/tree/master/example/counter

Flutter の BLoC(Business Logic Component)のライフサイクルを正確に管理して提供する Provider パッケージの解説
https://medium.com/flutter-jp/bloc-provider-70e869b11b2f

pipe method - Stream class - dart:async library - Dart API - Flutter API
https://api.flutter.dev/flutter/dart-async/Stream/pipe.html

Rxで知っておくと便利なSubjectたち - Qiita
https://qiita.com/ralph/items/f7205c8171826cc2153b