🥣

flutter_hooksのuseEffectを使う際の注意点

2021/04/27に公開

flutter_hooksを使いたかった

状態管理パッケージのRiverpodを導入しました。
同時に併用を推奨されているflutter_hooksも使い始めました。

useEffect

一度のみ実行したい場合や開放処理をする時に便利です。
初期化処理などでよく使うと思います。

ソースコード

  • State
class SampleStateController extends StateNotifier<SampleState> {
  SampleStateController() : super(SampleState());
  Future<void> getPackage() async {
    final package = await sample.getPackage(hogehogekey);
    state = state.copyWith(package: package);
  }
}
  • View
final sampleProvider StateNotifierProvider((ref) => SampleStateController());

class HooksSample extends HookConsumerWidget {
  Widget build(BuildContext context, WidgetRef ref) {
    final sample = ref.watch(sampleProvider.notifier);
    useEffect(() {
      sample.getPackage();
      return sample.dispose;
    }, const []);
    return Container(
        child: ...
    );
  }
}

問題

useEffectの中でStateをかえる処理をすると下記のエラーが出てしまいました。

E/flutter (32049): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: setState() or markNeedsBuild() called during build.
E/flutter (32049): This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.

ビルド中にStateを変えてはいけませんと怒られました。

じゃあビルドが終わってからStateを変えれば問題ないんでしょ?

WidgetsBinding.instance.addPostFrameCallback()を使えばクリアできそうです。
Widgetが描画されてから処理を実行してくれる便利なヤツ。
ソースコード

class HooksSample extends HookConsumerWidget {
  Widget build(BuildContext context, WidgetRef ref) {
    final sample = ref.watch(sampleProvider.notifier)
    useEffect(() {
      WidgetsBinding.instance.addPostFrameCallback((_) {
      	sample.getPackage();
      });
      return sample.dispose;
    }, const []);
    return Container(
        child: ...
    );
  }
}

無事解決

flutterもdartもまだまだ勉強中ですが、誰かの役に立てばと思い、書きました。
間違っているところや、もっとこうした方が良い等ありましたら、教えてくださると幸いです。
ありがとうございました。

Discussion