19
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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を押すとカウントアップされるというシンプルなものです。

Simulator Screen Shot - iPhone 8 - 2019-07-24 at 21.41.46.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の2つの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が入って画面がちらつくことを防ぐことができます。

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

また、RxDartでは、scanpipeを使うことで、以下のように書くこともできます。(pipeはDart標準の Stream APIにもあります

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 class
https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html

PublishSubject 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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
19
Help us understand the problem. What are the problem?