family修飾子とautoDispose修飾子について(RiverPod)
この記事では、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にテキストフィールドの入力などからの値を保持するような処理がある場合、データがそのまま残ることになり、期待された動きにならない可能性があります。
関連記事
参考記事
Discussion