Open9

NotifierとAsyncNotifier🐶

わいすけわいすけ

Riverpodの2系から追加されたNotifierAsyncNotifierについて使ってみた自分の感想などなど。

わいすけわいすけ

使い方

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.watchref.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について

StateNotifierdispose()があった。

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++;
  }
}
わいすけわいすけ

autoDisposefamily

NotifierとAsyncNotifierでautoDisposefamilyを使用したい場合、継承するクラスが変わるみたい?

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();
  }
}