1. youmeee

    Posted

    youmeee
Changes in title
+【Flutter】ページの初期化時に一度だけBlocの処理をコールしたい
Changes in tags
Changes in body
Source | HTML | Preview

やりたかったこと

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

間違いあれば指摘いただけると助かります!最後までご覧いただきありがとうございました。