1. youmeee

    Posted

    youmeee
Changes in title
+【Flutter】ページの初期化時に一度だけBlocの処理をコールしたい
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,140 @@
+# やりたかったこと
+
+FlutterでBlocパターンでの実装において、StatefulWidgetの初期化時に一度だけ、Blocの処理をコールしたい、!
+
+具体的には、トップページの初期化時に一度だけ新着ポップアップ通知の確認や、初回チュートリアルの表示確認をしたいケースでした。
+
+TopPage,Blocは以下のような実装になります。
+
+```top_page.dart
+class TopPage extends StatefulWidget {
+ @override
+ State<StatefulWidget> createState() => TopPageState();
+}
+
+class TopPageState extends State<TopPage> {
+ TopBloc _bloc;
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _bloc = Provider.of<TopBloc>(context);
+ }
+
+ // _bloc.checklaunchAction.add()をどこで呼ぶ?
+
+ @override
+ Widget build() {
+ ...
+ }
+}
+
+class TopBloc {
+
+ final PublishSubject<void> _checkLaunchActionController = PublishSubject();
+
+ Sink<void> get checkLaunchAction => _checkLaunchActionController.sink;
+
+ TopBloc() {
+ _checkLaunchActionController.listen((_) {
+ // ポップアップの確認処理など
+ });
+ }
+
+ dispose() {
+ // controllerのdispose処理
+ }
+}
+```
+
+`TopBloc`,`TopPage`は以下のようにProviderを経由して、生成されます。これをすることで、TopPage内で`Provider.of<TopBloc>(context)`でBlocを参照することができます。
+
+```hoge.dart
+Provider<TopBloc>(
+ child: TopPage(),
+ builder: (context) => TopBloc(),
+ dispose: (_, bloc) {
+ bloc.dispose();
+ },
+);
+```
+
+## 案1: initState()で呼ぶ
+
+Providerを使って、Blocを初期化し、その子ウィジェットとしてStatefulWidgetを生成するため、initStateの段階では、Provider.of(context)で必要なContextが取得できないため却下。
+
+## 案2: didChangeDependencies()で呼ぶ
+
+このコールバックでは、contextが取得できるため、Blocの参照をState側のプロパティで保持することをよくやりますが、画面遷移をしたりすると、何度も呼ばれる可能性があるため、却下。
+
+## 案3: Blocのコンストラクタで呼ぶ
+
+Blocのコンストラクタで、先に確認処理を実行しておき、その処理結果をBlobで保持しておくという方法もありますが、これだとBlocに結果を問い合わせるタイミングが難しいことや、処理が追いづらくなるという点で却下になりました。
+
+# 結論
+
+StateのinitStateにて、`WidgetsBinding.instance.addPostFrameCallback()`内に一度だけ呼び出したいものを記述する。
+
+これをすることで、Widgetの最初にビルドが完了した後に呼ばれることが保証されるため、必然的にBlocが取得でき、一度だけ呼ばれるということも保証されます。
+
+```top_page.dart
+class TopPage extends StatefulWidget {
+ @override
+ State<StatefulWidget> createState() => TopPageState();
+}
+
+class TopPageState extends State<TopPage> {
+ ...
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ // ここで処理を呼び出す
+ _bloc.checkLaunchAction.add(null);
+ });
+ }
+
+ ...
+}
+```
+
+また、以下のようにmixinを定義して、あたかも新しいライフサイクルコールバックかのように書くこともできます
+
+```initial_runnable_state.dart
+mixin InitialRunnableState<T extends StatefulWidget> on State<T> {
+ void runOnInitialBuild();
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ runOnInitialBuild();
+ });
+ }
+}
+```
+
+```top_page.dart
+class TopPageState extends State<TopPage> with InitialRunnableState<TopPage> {
+
+ ...
+
+ @override
+ void runOnInitialBuild() {
+ // ここで処理を呼び出す
+ _bloc.checkLaunchAction.add(null);
+ }
+
+ ...
+}
+```
+
+こちらのmixinについては、以下のリポジトリに公開しています。
+https://github.com/youmitsu/InitialRunnableState
+
+pub.devにも公開しました
+https://pub.dev/packages/initial_runnable_state
+
+
+間違いあれば指摘いただけると助かります!最後までご覧いただきありがとうございました。