NotifierとAsyncNotifier🐶
Riverpodの2系から追加されたNotifierとAsyncNotifierについて使ってみた自分の感想などなど。
使い方
NotifierとStateNotifierで比較してみます。
Notifierの場合
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
class Counter extends Notifier<int> {
@override
int build() {
return 0;
}
void increment() {
state++;
}
}
StateNotifierの場合
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() {
state++;
}
}
Widget側での使用方法はどちらも一緒です。
Consumer(
builder: (context, ref) {
return Text('count: ${ref.watch(counterProvider)}');
},
)
Consumer(
builder: (context, ref) {
return ElevatedButton(
onTap: () => ref.read(counterProvider.notifier).increment(),
child: const Text('increment'),
);
},
)
NotifierとAsyncNotifierはbuild内でref.watchやref.listenを使用することができる。
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
class Counter extends Notifier<int> {
@override
int build() {
// build内ではref.watchやref.listenを使用することができる。
// watchしているProviderに変更があればこのNotifierも再構築が走る
final hoge = ref.watch(hogeProvider);
return 0;
}
void increment() {
state++;
// ref.readを使用して他のProviderの操作もできる
ref.read(counterProvider2.notifier).update((s) => s + state);
}
}
StateNotifierでRefを使用したい場合は、下のように渡してあげる必要があったけど、NotifierとAsyncNotifierは不要。
final counterProvider = StateNotifierProvider<Counter, int>(Counter.new);
class Counter extends StateNotifier<int> {
Counter(this._ref) : super(0);
final Ref _ref;
void increment() {
state++;
_ref.read(counterProvider2.notifier).update((s) => s + state);
}
}
disposeについて
StateNotifierはdispose()があった。
class Counter extends StateNotifier<int> {
Counter(this._ref) : super(0);
final Ref _ref;
void increment() {
state++;
}
@override
void dispose() {
print('StateNotifier disposed.');
super.dispose();
}
}
Notifierの場合は、ProviderやFutureProviderなどの他のProviderたちのように、build内で定義する。
class Counter extends Notifier<int> {
@override
int build() {
ref.onDispose(() {
print('Notifier disposed.');
});
return 0;
}
void increment() {
state++;
}
}
ちなみStateNotifierにはmountedプロパティがあったけどNotifierとAsyncNotifierには無い。
これはStateNotifieirとNotifier/AsyncNotifierでは廃棄のサイクルが違うからだそう。
autoDisposeとfamily
NotifierとAsyncNotifierでautoDisposeとfamilyを使用したい場合、継承するクラスが変わるみたい?
final counterProvider = AutoDisposeNotifierProvider<Counter, int>(Counter.new);
class Counter extends AutoDisposeNotifier<int> {
これはちょっとめんどいかも...って感じたけどriverpod_generatorを使えば特に意識しなくても書けそう。
@riverpodまたは@Riverpod(keepAlive: false)のアノテーションでautoDispose
@Riverpod(keepAlive: true)でautoDisposeされない
@riverpod
class HogeNotifier extends _$HogeNotifier {
@override
String build(String hoge) { // familyを使いたい場合はbuildの引数に入れる
return hoge;
}
}
NotifierやAsyncNotifierを定義するたびにbuild_runner走らせるのはちょい面倒かなとも感じる。
ただfamilyを使う場合、引数に入れられるのが1つだったのがriverpod_generatorを使えば複数入れることも可能になる。
familyに入れられるのはプリミティブ型もしくはequatable(freezedでも可)なクラス。
dartにメタプロが入るまで待つというのも1つの選択肢かな。
AsyncNotifier
riverpod_generatorを使ってAsyncNotifierを継承クラスを生成するためには、buildの返り値をFutureOr<T>で定義する(Future<T>でも可)
@riverpod
class HogeNotifier extends _$HogeNotifier {
@override
FutureOr<String> build() {
return 'hoge';
}
}
こうするとstateがAsyncValue<T>になる。
StateNotifierでstateをAsyncValue<T>で扱う時は、stateの初期化処理的なものを書いていたけど、AsyncNotifierなら自然な感じで書けていいかも。
Streamを扱う時は??
無難にStreamProviderを使うのがいいと思う。
Notifier/AsyncNotifierを使う場合は、多分AsyncNotifierではなく、NotifierのstateをAsyncValue<T>で使った方がわかりやすいかも?
AsyncNotifierのbuildはFutureOr<T>なのでStreamを扱うとちょっと違うかなという感じ。
なのでStreamを扱いたい場合は、StateNotifierと同じようにNotifier<AsyncValue<T>>のようにした方がいいかも。
class HogeNotifier extends Notifier<AsyncValue<String>> {
StreamSubscription<String>? _subscription;
@override
AsyncValue<String> build() {
ref.onDispose(() {
_subscription?.cancel().then((_) => _subscription = null);
});
_subscription ??= hogeStream.listen((hoge) => state = AsyncData(hoge));
return const AsyncLoading();
}
}
更新忘れてたけど、StreamNotifier追加されたね!