Flutter状態管理　Riverpod(StateNotifier) x Freezed

# Flutterにおける状態管理
**Riverpod**と**Freezed**を用いた状態管理まとめ

## 想定読者
- Flutterの最低限の知識がある
- Providerにおける状態管理の構造を理解している
必ずしもProviderを理解しておく必要はないが、Providerを理解していると、よりRiverpodも理解しやすくなる
- RiverpodでstateProviderでの状態管理を理解している。

その上で、StateNotifierの理解に苦しんでいる方

## 導入方法

**Riverpodの導入**
Riverpodの導入方法は[Riverpod公式Document](https://riverpod.dev/docs/getting_started)を参照してください。
pubspec.yamlを編集してもいいが、以下のようにターミナルで行うと便利
```terminal
$ flutter pub add 追加したいパッケージ名
```
**Freezedの導入**
importするのはfreezed_annotaionのみで、下2つはbuildするときに必要となる。
```terminal
$ flutter pub add freezed_annotation
$ flutter pub add --dev freezed
$ flutter pub add --dev build_runner
```

## StateNotifier における状態管理の構造
大きく分けて3つのclassを用いる

- 状態変数をまとめたclass
- 状態の操作をするclass
- UIから状態にアクセスすることを可能にするclass


これらを簡単にまとると以下のようになる

1. **状態変数をまとめたclass**
このclass内で**状態変数**を定義する。このclassは状態変数の保守性を高めるためにimmutableに宣言する。immutableであるが、もちろんイベント（2で定義する）を応じてかえることができる。
2. **状態を操作をするclass**
このclassでは1で定義した**状態変数を変更するメソッド**を定義する。
StateNotifierを継承する。
stateで、1で定義された状態変数にアクセスすることができる。
changeNotifierとは異なり値の変更を通知する際に、NotifyListers()が不要で
stateが更新されると自動的に値の変更を通知する。
1で書いたように、stateはimmutableであるため、freezedパッケージを持ちいない場合は新たに1のclassを作り直す必要があり、コードが冗長になる。
3. **UIから状態にアクセスすることを可能にするclass**
StateNotifierProviderを継承する。
UIから状態にアクセスする際には、このclassを通じて行う

## 実装

例としてユーザー情報を管理するStateNotifierを作ってみる。

### Providerの実装
管理する状態変数は
- UserName
- Email
- age

の3つをとする。

```userdataProvider.dart
import 'package:freezed_annotation/freezed_annotation.dart';

// ファイル名.freezed.dart
part 'userdataProvider.freezed.dart';

// 以下で示すbuildをするまではErrorが出ると思うが、無視して良い。
// 状態変数を定義するclass
@freezed
// _$UserDataをmixinし、コンストラクタに_UserDataを代入する
class UserData with _$UserData {
  const factory UserData({
    // @Default(初期値) 型　変数名
    // @Defaultは不要であるが、NullSafety対策にもなる
    @Dafault('') String userName,
    @Default('') String email,
    @Default(0) int age,
  })  = _UserData;
}
```
次にGeneratorを走らせる。UserData.freezed.dartというファイルが生成されエラーが解消される。
（UserData classに変更を加える度にこのコマンドを走らせる必要がある）
Freezedに関連するエラー以外がある場合にはうまくいかない。
```terminal:terminal
$ flutter pub run build_runner build --delete-conflicting-outputs
```

次に**状態を操作をするclass**は次のようになる
```userdataProvider.dart
// StateNotifierを継承する
class UserDataNotifier extends StateNotifier<UserData>{
  // 初期化
  UserDataNotifier() : super(const UserData());

  // 状態を変更するメソッド
  // Usernameを変更するメソッド
  void changeUserName(String newUserName) {
    // freezedパッケージによりcopywithが使える。
    // stateはimmutableであるが、copywithを使うことで、簡単に書ける。
    state = state.copywith(userName: newUserName);
  }

  // 年齢を変更するメソッド
  // 上と同様に書ける
  void changeAge(int newAge) {
    state = state.copywith(age: newAge);  
  }

  // ...
}
```

最後に**StateNotifierProvider**の実装

```userdataProvider.dart
// providerはglobalで宣言　immutableのため特に気にする必要はない
// StateNotifierProvider<2のclass, 1のclass>
// return 2のclass
final userDataProvider = StateNotifierProvider<UserDataNotifier, UserData>((ref) {
  return UserDataNotifier();
});
```

### UIに表示させる
次にUI側で状態変数にアクセスしたり、状態変数を変更したりする部分の実装。
そのためには、ref オブジェクトを参照する必要があり、そのためのWidgetが以下のようにriverpodには用意されている。
|Flutter|Riverpod|
|----|----|
|StatelessWidget|ConsumerWidget|
|StatefulWdiget|ConsumerStatefulWidget|
|State|ConsumerState|

これらのWidgetは、従来のFlutterのWidgetと大差はないが、buildメソッドに対してBuildContextに加え、WidgetRefを引数として追加する。

また、ConsumerというWidgetが用意されており、refオブジェクトに参照するためにclassを作らなくてもよい。

ref.watch, ref.read, ref.listenについても触れておく
- **ref.watch**
状態変数を監視し、変化するたびにWidgetをRebuildする。
initStateや、onPressed内といった非同期処理ではwatchは使われるべきではなく、その代わりにref.readを使う
- **ref.read**
状態変数は監視しないが、その時点でのstateを取得することができる。
普通、ボタンが押されたときの処理などに使われる。
[公式document](https://riverpod.dev/docs/concepts/reading/)によると、ref.readはできるだけ使用を避けるべきとのこと。
- **ref.listen**
基本はref.watchと同じであるが、ref.listenは任意の関数を呼び出すことができる
注）今回は扱わない


以下では、ConsumeWidgetを継承したUserNameを変更するclassと、Consumerを使った年齢を表示するWidgetである。
```main.dart
class UserSettings extends ConsumerWidget {
  const UserSettings({Key? key}) ; super(key:key);
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Column(
      children:[
        // ...
        TextFormField(
          // ...
          onChanged:(String value){
              // 値を変更するときは、次の様に行う
              // ref.read(StateNotifierProviderを継承するclass.notifier).メソッド
              ref.read(UserDataProvider.notifier).changeUserName(value);
            },
           // ...
        ),
        // ...
      ],
    );
  }
}
```
```main.dart
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) ; super(key:key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body:Center(
        child:Column(
          children:[
            // ...
            Consumer(
             builder: (context, ref, child) {
               return Text(
                 // 状態変数にアクセスしたいときは、
                 // ref.watch(Provider).状態変数名
                 // 値が変更されたときにrebuildされるのはConsumer内のWidgetである、
                 // できるだけConsumerは小さく。
                 ref.watch(userDataProvider).userName,
               );
             }
            ),
            // ...
          ],
        );
      );
    );
  }  
}
```


## 参考文献
Riverpod公式Document

https://riverpod.dev/docs/getting_started

freezed Dart Package 

https://pub.dev/packages/freezed

https://itome.team/blog/2020/05/flutter-state-notifier-provider/

## まとめ
自分が、StateNotifierを理解するのに苦労したため、まとめてみた。
StateNotifierProviderが自分で実装できるようになると、より複雑な状態管理が必要になっても、シンプルな形で書けるようになり非常に便利であると感じた。

今後、さらにhooksを加えて、 
**Riverpod x Freezed x Hooks**
などで状態管理もしてみたい