NotifierとAsyncNotifier🐶
Notifier
とAsyncNotifier
について使ってみた自分の感想などなど。
Riverpodの2系から追加された使い方
NotifierとStateNotifierで比較してみます。
Notifierの場合
final counterProvider = NotifierProvider<Counter, int>(Counter.new);
class Counter extends Notifier<int> {
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> {
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++;
}
void dispose() {
print('StateNotifier disposed.');
super.dispose();
}
}
Notifierの場合は、ProviderやFutureProviderなどの他のProviderたちのように、build内で定義する。
class Counter extends Notifier<int> {
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
されない
class HogeNotifier extends _$HogeNotifier {
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>でも可)
class HogeNotifier extends _$HogeNotifier {
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;
AsyncValue<String> build() {
ref.onDispose(() {
_subscription?.cancel().then((_) => _subscription = null);
});
_subscription ??= hogeStream.listen((hoge) => state = AsyncData(hoge));
return const AsyncLoading();
}
}
更新忘れてたけど、StreamNotifier追加されたね!