前回までの記事
このシリーズでは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を押すとカウントアップされるというシンプルなものです。
ライブラリ
dependencies:
# 省略
provider: 3.0.0+1
rxdart: 0.22.1
BLoC
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を用いています。
アプリの流れとしては、
- コンストラクタで actionController の stream に値が流れてきたときのリスナーを設定
- ボタンのタップ時に increments.add(null) でタップされたことをBlocに通知
- 2 をトリガーとして 1 で設定したロジックにより count を更新
- count を StreamBuilder で表示
Widget
以下にWidget側を示します。 main.dart は、ほぼFlutterのデフォルトカウンターアプリです。
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では、scan
とpipe
を使うことで、以下のように書くこともできます。(pipeはDart標準の Stream APIにもあります)
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