🗽

[Flutter] Riverpodによる状態管理まとめ

2023/04/15に公開

3ヶ月前くらいにFlutterプロジェクトに参戦しました.

このプロジェクトで初めてFlutterを使い初めたので先人のコードにならって状態管理のためにsetStateを使用していたが色々辛いところが出てきたので状態管理方法を改めたい.

今回はRiverpodによる状態管理の方法について軽くまとめる.

Flutterでの状態管理を考えた時に

  • setStateメソッドを使用したStatefulWidgetでの管理
  • Riverpodでの管理
  • blocパッケージを使用した管理

が有名どころとして挙げられる.

setStateによる管理は小規模なアプリケーションの状態管理では十分かつシンプルに記述可能だが, 規模感が少しでも大きくなってくると次のような理由で開発が難しくなってしまう.

  • Widget内で状態を管理管理+Viewが混合するためロジックと見た目の部分の分離ができず複雑化してしまう.
  • setStateは呼び出されるたびに際ビルドがかかる. 再描画が頻繁に行われるとパフォーマンス上よろしくない.

他にも理由があるがこのような理由で状態管理方法を改めたい.

今回はRiverpodを使用するので以下に概要と使い方をまとめる
今度blocでの実装をしてみてどっちが良さそうか検証したい

Riverpodによる状態管理

これが一番人気っぽい.
RiverpodはProviderパターンによって状態管理を行うためのライブラリでproviderと呼ばれるライブラリの上位互換の様. providerは軽量なライブラリである利点はあるがRiverpodではシンプルに状態管理の記述ができ大規模で使うならRiverpodを使うのが良さそう.
Riverpodライブラリ内で定義されるStateNotifier classを継承させて以下の様にCounterとういう状態管理クラスを定義する.

superの引数に入っている0が初期値となる.
このCounterクラスにincrementとdecrementという関数を定義してstateの変更を行なっている.

import 'package:state_notifier/state_notifier.dart';

class Counter extends StateNotifier<int> {
  Counter() : super(0);

  void increment() {
    state++;
  }

  void decrement() {
    state--;
  }
}

final counterProvider = StateNotifierProvider((ref) => Counter());

StateNotifierクラスを継承したCounterクラスを作成して状態管理クラスとして定義する.
これに加えてStateNotifierProviderでCounterをラップしてcounterProviderを表示する側のWidgetで呼び出す事で状態の監視, 更新を行うことができる.

ここで監視を定義する時に2通りの定義方法があるようで

final counter = useProvider(counterProvider);
final counter = ref.watch(counterProvider)

2つの違いを見てみると,

useProviderはWidgetがビルドされる時にProviderを読み込み,そのProviderに関連する状態変更が発生していた場合には再ビルドが走る仕様になっている.

Widget build(BuildContext context) {
    final counter = useProvider(counterProvider);

    return Text(counter.state.toString());
}

ref.watch(counterProvider)という書き方は、Providerの値を監視し、値が変更された場合に再ビルドするようで, Widget以外の場所で状態を参照するために使用できる. よってref.watchは、手動でProviderを監視して必要に応じて再レンダリングするのでWidgetのbuildメソッド以外でProviderを使用する場合に適しているよう.

  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    return Text(counter.state.toString());
  }

これに加えて
ref.watch(counterProvider.notifier)という書き方もできる.

これは
StateNotifierオブジェクト自体を監視することでStateNotifierオブジェクトが状態を変更した場合のみ再ビルドすることができる.

useProviderを使用してProviderを読み込んだ場合, 状態の変更が発生するたびに再ビルドすると無駄な再ビルドが発生してしまう.
したがってref.watchを使用してProviderが提供するStateNotifierオブジェクト自体を監視し、StateNotifierオブジェクト"が"状態を変更した場合に再ビルドするようにすることができる.

  Widget build(BuildContext context) {
    final counter = ref.watch(counterProvider.notifier);

    return Text(counter.state.toString());
  }

このようにcounterProvider.notifierを関しすることウィジェット外でのステートの変更による再レンダリングを抑えることができる.

こんな感じで状態管理クラスを分離させることができた.

Discussion