💨

family修飾子とautoDispose修飾子について(RiverPod)

2023/05/25に公開

この記事では、family修飾子とautoDisposeの説明と活用法。また、これらを学ぶにあたって、必要だったObjectが持っている==演算子とhashcodeプロパティについて触れます。


autoDispose修飾子

この修飾子を付け加えられたproviderは、参照されなくなった際に、ステートが自動的に破棄されるようになります。
アプリの設計にもよりますが、参照されなくなったproviderのステートは破棄するのが望ましい。次のようなケースのため。

Firebase 使用時に、サービスとの接続を切って不必要な負荷を避けるため。
ユーザが別の画面に遷移してまた戻って来る際に、ステートをリセットしてデータ取得をやり直すため。

family修飾子

.family 修飾子の目的は「外部のパラメータをもとに一意のプロバイダを作成すること」です。
.family 修飾子を付けてプロバイダを作成すると、パラメータが追加されます。 このパラメータはプロバイダのステート(状態)を計算する要素として使用することができます。

immutableなオブジェクトとhashcode

Dartのイミュータブルオブジェクトの特性は、プリミティブ型(数値、文字列など)やconstで生成されたオブジェクト(例えば、const [])に限定されます。これらのイミュータブルなオブジェクトは、同じ値であれば同一のインスタンスを再利用し、同じhashCodeを持ちます。

family修飾子とautoDispose修飾子の動作を確認

ケース1:autoDispose修飾子を付けて、渡す引数を違うオブジェクトにしている場合

final practiceModifiersProvider = StateNotifierProvider.family
    .autoDispose<PracticeModifierNotifier, List<String>, Person>(
        (ref, Person person) => PracticeModifierNotifier([person.id], person));

class PracticeModifierNotifier extends StateNotifier<List<String>> {
  PracticeModifierNotifier(List<String> state, person) : super(state) {
    print(this.hashCode); //このクラスのオブジェクトのhashcode
    print(state.hashCode); //stateのhashcode
  }
}
class PracticeModifiersPage extends ConsumerWidget {
  const PracticeModifiersPage({Key? key}) : super(key: key);
  
  Widget build(BuildContext context, WidgetRef ref) {
    final random = math.Random();
    final id = random.nextInt(9999);
    final person = Person(id: id.toString(), name: 'Takeru');
    print('build() person id: ${person.id}');
    print('person hashcode ${person.hashCode}');
    final state = ref.watch(practiceModifiersProvider(person));
    print('state hashcode in build: ${state.hashCode}');
    

notifierのクラスのインスタンス時にnotifierオブジェクトとstateのhashcodeを表示させています。stateに入れる初期値が毎度同じになると同様のhashcodeになるので、それを避けるため、乱数を用いて違うpersonが生成し、providerに渡しています。

実行結果

flutter: build() person id: 1133
flutter: person hashcode 225940167
flutter: 570428963
flutter: 27477662
flutter: state hashcode in build: 27477662
flutter: build() person id: 4809
flutter: person hashcode 851939674
flutter: 123737457
flutter: 669179967
flutter: state hashcode in build: 669179967

このコードだと前のページに戻るなどでproviderが参照されなくなった時点でnotifierもstateも破棄される。そして、pageが再度buildされる時に新しく別のオブジェクトを生成してるのが分かります。

ケース2:autoDispose修飾子を外し、渡す引数を違うオブジェクトにしている場合

final practiceModifiersProvider = StateNotifierProvider.family<
        PracticeModifierNotifier, List<String>, Person>(
    (ref, Person person) => PracticeModifierNotifier([person.id], person));

class PracticeModifierNotifier extends StateNotifier<List<String>> {
  PracticeModifierNotifier(List<String> state, person) : super(state) {
    print(this.hashCode); //このクラスのオブジェクトのhashcode
    print(state.hashCode); //stateのhashcode
  }
}

autoDispose修飾子を外しても、動作に変わりはありませんでした。これは、family修飾子が「外部のパラメータをもとに一意のプロバイダを作成すること」なので、パラメータがの値が違うから再度stateを作り直しているのだと思われるが、公式を見ても詳しい記載がありませんでした。

ケース3:autoDispose修飾子を外し、渡す引数を同一のオブジェクトにした場合

class PracticeModifiersPage extends ConsumerWidget {
  const PracticeModifiersPage({Key? key, required this.person})
      : super(key: key);
  final Person person;

  
  Widget build(BuildContext context, WidgetRef ref) {
    final random = math.Random();
    final id = random.nextInt(9999);
    //final person = Person(id: id.toString(), name: 'Takeru');
    print('build() person id: ${person.id}');
    print('person hashcode ${person.hashCode}');
    final state = ref.watch(practiceModifiersProvider(person));
    print('state hashcode in build: ${state.hashCode}');

今度はautoDisposeは外したまま、引数として渡すpersonを毎回同一のオブジェクトになるように変更しました。
実行結果

flutter: build() person id: 11111
flutter: person hashcode 385511266
flutter: 927846313
flutter: 424708756
flutter: state hashcode in build: 424708756
flutter: build() person id: 11111
flutter: person hashcode 385511266
flutter: state hashcode in build: 424708756

同一のオブジェクトを引き渡した場合、stateは破棄されず、pageが再度buildされてもstateは再生性もされていません。

この結果を見る限り、autoDispose修飾子を付けないと、引数が同一のオブジェクトの場合、stateを再生性せず、以前のstateを引き続き用います。例えば、この場合、notifierやstateにテキストフィールドの入力などからの値を保持するような処理がある場合、データがそのまま残ることになり、期待された動きにならない可能性があります。

関連記事

https://zenn.dev/shoyu88/articles/6d083865d8e841

参考記事

https://riverpod.dev/ja/docs/concepts/modifiers/auto_dispose

Discussion