やりたかったこと
FlutterでBlocパターンでの実装において、StatefulWidgetの初期化時に一度だけ、Blocの処理をコールしたい、!
具体的には、トップページの初期化時に一度だけ新着ポップアップ通知の確認や、初回チュートリアルの表示確認をしたいケースでした。
TopPage,Blocは以下のような実装になります。
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を参照することができます。
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が取得でき、一度だけ呼ばれるということも保証されます。
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を定義して、あたかも新しいライフサイクルコールバックかのように書くこともできます
mixin InitialRunnableState<T extends StatefulWidget> on State<T> {
void runOnInitialBuild();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
runOnInitialBuild();
});
}
}
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
間違いあれば指摘いただけると助かります!最後までご覧いただきありがとうございました。