Help us understand the problem. What is going on with this article?

【Flutter】ページの初期化時に一度だけBlocの処理をコールしたい

やりたかったこと

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

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

youmeee
Androidエンジニアやってます。Kotlinかわいい。最近はFlutterばかりやってます
bla-bo
Blabo!はちょっとしたひらめきで、商品をつくれるコミュニティです。
https://bla.bo/
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした