😇

StateNotifier は推奨されなくなった!

2024/06/05に公開

対象者

  • riverpod使ったことがある人
  • StateNotifier使ったことがある人
  • riverpod2.0使ったことがある人

こちらの完成品を参考にしてください

プロジェクトの説明

https://riverpod.dev/ja/docs/migration/from_state_notifier

Riverpod 2.0 とともに、新しいクラスが導入されました: Notifier / AsyncNotifer。

StateNotifier は、これらの新しい API を優先して非推奨になりました。

このページでは、非推奨の StateNotifier から新しい API に移行する方法を説明します。

AsyncNotifier によって導入された主な利点は、より優れた非同期サポートです。実際、AsyncNotifier は、UI から変更する方法を公開できる FutureProvider と考えることができます。

さらに、新しい (非同期)Notifiers は、次の機能を備えています:

  • クラス内で Ref オブジェクトを公開
  • コード生成アプローチと非コード生成アプローチの間で同様の構文を提供
  • 同期バージョンと非同期バージョンの間で同様の構文を提供
  • ロジックをプロバイダーから移動し、Notifiers 自体に集中化

なんてこった非推奨になってしまったか😇
移行するしかないか...

Old Code

昔からあるStateNotifierですね。 Ref, late, dispose etc.......
たくさん書いてあるな😅

Riverpod2.0からはもっと短く書けるらしい?

タイマー作るよ

タイマー作って古いコードと新しいコードの比較してみますか。

[Repository]

class Repository {
  Future<void> update(int value) async {
    await Future.delayed(const Duration(seconds: 1));
  }
}

[StateNotifier]

import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:provider_tutorial/application/repository/repository.dart';

final repositoryProvider = Provider<Repository>((ref) {
  return Repository();
});

final durationProvider = Provider<Duration>((ref) {
  return const Duration(seconds: 1);
});

class MyNotifier extends StateNotifier<int> {
  MyNotifier(this.ref, this.period) : super(0) {
    // 1 init logic
    _timer = Timer.periodic(period, (t) => update()); // 2 side effect on init
  }
  final Duration period;
  final Ref ref;
  late final Timer _timer;

  Future<void> update() async {
    await ref.read(repositoryProvider).update(state + 1); // 3 mutation
    if (mounted) state++; // 4 check for mounted props
  }

  Future<void> reset() async {
    await ref.read(repositoryProvider).update(0); // 3 mutation
    if (mounted) state = 0; // 4 check for mounted props
  }

  
  void dispose() {
    _timer.cancel(); // 5 custom dispose logic
    super.dispose();
  }
}

final myNotifierProvider = StateNotifierProvider<MyNotifier, int>((ref) {
  // 6 provider definition
  final period = ref.watch(durationProvider); // 7 reactive dependency logic
  return MyNotifier(ref, period); // 8 pipe down `ref`
});

New Code

コードを見てみると、コンストラクターに初期化された時の処理を書いていたのが、buildメソッドの中に移動してる。dispose メソッドがなくなって、代わりに ref.onDispose になってますね。これでタイマーキャンセルして、状態 (state)を破棄するのかな。

import 'dart:async';

import 'package:provider_tutorial/application/repository/repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'my_notifier.g.dart';

(keepAlive: true)
Repository repository(RepositoryRef ref) {
  return Repository();
}


Duration duration(DurationRef ref) {
  return const Duration(seconds: 1);
}


class MyNotifier extends _$MyNotifier {
  
  int build() {
    // Just read/write the code here, in one place
    final period = ref.watch(durationProvider);
    final timer = Timer.periodic(period, (t) => update());
    ref.onDispose(timer.cancel);

    return 0;
  }

  Future<void> update() async {
    await ref.read(repositoryProvider).update(state + 1);
    // `mounted` is no more!
    state++; // This might throw.
  }

  Future<void> reset() async {
    await ref.read(repositoryProvider).update(0);
    state = 0;
  }
}

[Viewに表示してみるか...]

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:provider_tutorial/application/new/my_notifier.dart';
import 'package:provider_tutorial/presentation/theme/theme_color.dart';

class MyView extends ConsumerWidget {
  const MyView({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final myNotifier = ref.watch(myNotifierProvider);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: ThemeColor.green,
        title: const Text('Bugs Encountered'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(myNotifierProvider.notifier).update(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$myNotifier',
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            ElevatedButton(
                onPressed: () async {
                  await ref.read(myNotifierProvider.notifier).reset();
                },
                child: const Text('Reset')),
          ],
        ),
      ),
    );
  }
}

こちらのデモアプリですが、タイマーが止まりません笑
停止ボタン押しても止まらないので削除することをオススメします。なぜだろうか....

感想

Riverpod2.0から使える新しいNotifierクラスは、コードの記述量の減少以外にもコードの自動生成のAPIを提供してくれるので開発を楽にしてくれるようですね。古いコードの知識もないと、メリットも仕組みもわからないですが😅

筆者は、去年まだレガシーなriverpodのコードを書くプロジェクトにアサインされたことがあるので、古いコードの使いずらさを知っております。勉強にはなったが...

新しいバージョンのメリットは・:*+.(( °ω° ))/.:+

クラス内で Ref オブジェクトを公開
Refメソッド書いてなかったですね笑

コード生成アプローチと非コード生成アプローチの間で同様の構文を提供
自動生成しないコードもあるな。

Discussion