状態管理 (Flutter Roadmap State Management)

## はじめに
Flutterを網羅的に学習するにあたって[Roadmap](https://roadmap.sh/flutter)を使って学習を進めることにしました。

この記事では、Flutter初学者やこれからFlutterを学習し始める方に向けて、**状態管理**についてまとめています。

RoadmapはFlutterだけでなく、他の言語やスキルのロードマップも提供されており、何から学習して良いか分からないと悩んでいる方にとって有用なサイトになっています。
ぜひ[Roadmap](https://roadmap.sh/)を利用して学習してみてください。

## Roadmapとは

簡潔に言えば、Roadmap.shは学習者にとっての**ガイドブック**であり、**学習の方向性を提供する学習ロードマップサイト**です。

初心者から上級者まで、ステップバイステップでスキルを習得するための情報が提供されています。

学習の進め方が分かりやすく示されているだけでなく、個々の項目に参考資料やリソースへのリンクも提供されているので、学習者は目標を設定し、自分自身のペースで学習を進めることができます。

## State Management
FlutterロードマップState Managementでは以下の6つのサイトが紹介されています。興味のある方はぜひお読みください。

- State management in Flutter: https://docs.flutter.dev/data-and-backend/state-mgmt
- Intro to State Management: https://docs.flutter.dev/data-and-backend/state-mgmt/intro
- ValueNotifier class - Flutter: https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html
- ValuerNotifier & ValueListenableBuilder: https://medium.com/@avnishnishad/flutter-communication-between-widgets-using-valuenotifier-and-valuelistenablebuilder-b51ef627a58b
- ChangeNotifier class - Flutter: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html
- Simple app state management: https://docs.flutter.dev/data-and-backend/state-mgmt/simple

## 状態管理とは
状態管理とは、アプリケーション内でデータの変更を追跡し、それに基づいてUIを更新するプロセスのことです。Flutter の UI は、状態 を `build()` メソッドで参照しながら構築される設計になっています。そしてその状態の整合性が破綻しないように、「状態をどういうフローでどこに保持するか」、「どのように状態の変更を検知してリビルドするか」ということについて、ルールを決めて扱うということをひとことで **状態管理** と言います。

Flutterにおいて状態は**state**と呼ばれ、state(状態)は大きく分けて２種類あります。

一つは **Ephemeral state** (ローカル状態 / エフェメラル状態)、もう一つは **App state** (共有状態) です。

`Ephemeral state` とは、クラス間で共有しない、スコープが閉じたデータ(状態)のことです。例えば ToDoアプリのリストがあります。各ToDoアイテムは、それぞれが削除されるか完了するかなどの状態を持ちます。このToDoアイテムごとの状態は、そのウィジェット内で管理されるのが基本です。ToDoアイテムの削除ボタンを押すと、そのアイテムのローカルな状態が変更され、ウィジェットがリビルドされることで即座に反映されます。基本的に他のクラスから参照されることはないので `Ephemeral state` と言えます。

一方 `App State` とは、複数のクラス間で共有されるデータ(状態)のことです。例えばアプリ全体で共有される状態の例として、ユーザーの認証状態があります。ユーザーの認証状態はアプリ全体で共有され、それに基づいてUIや機能が制御されます。認証が必要な画面では、ユーザーがログインしているかどうかに基づいて適切な画面が表示されため `App State` と言えます。

## ChangeNotifier
ChangeNotifierとは、Flutter SDKに含まれている、リスナーに変更通知を提供し、監視可能にさせるためのクラスです。ChangeNotifierを `extends` しているクラスは、インスタンスの中のメソッドが実行され、状態の変更が発生した際に `notifyListeners` でUIの更新をトリガーすることができます。

- **notifyListeners():** 登録されたリスナーに変更を通知するメソッドです。状態が変更されたときにこのメソッドが呼び出されます。

### サンプルコード
```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChangeNotifierProvider.value(
        value: CounterModel(),
        child: const MyHomePage(),
      ),
    );
  }
}

class CounterModel extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final counterModel = context.watch<CounterModel>();

    return Scaffold(
      appBar: AppBar(
        title: const Text('ChangeNotifier Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Counter: ${counterModel.counter}',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                counterModel.increment();
              },
              child: const Text('+1'),
            ),
          ],
        ),
      ),
    );
  }
}
```
- **CounterModel クラス:**
    - `ChangeNotifier`を継承したモデルクラスです。このクラスで状態を保持し、変更を通知します。
    - `_counter` プロパティでカウンターの値を格納します。
    - `increment` メソッドはカウンターを増やし、 `notifyListeners` メソッドで状態(`counter`)の変更を通知します。

- **MyHomePage クラス:**
    - カウンターの現在の値を表示し、増加ボタンがあるウィジェットです。
    - `context.watch<CounterModel>()` で `CounterModel` インスタンスを取得し、その変更を購読します。

- **ChangeNotifierProvider:**
    - `ChangeNotifierProvider` は `CounterModel` クラスを指定し、子ウィジェットでその変更を監視できるようにします。

## ValueNotifier
ValueNotifier とは、 `ChangeNotifier` を簡略化した状態管理クラスです。単一の値の変更を通知し、それに応じてリスナーが処理を実行できるようになります。 `ChangeNotifier` とは異なり、 `ValueNotifier` は `notifyListeners` メソッドを持たず、代わりに `value` プロパティを通じて現在の値にアクセスすることができます。

ValueNotifierは `ChangeNotifier` を継承(extends)し `ValueListenable` を実装(implements)したクラスです。 `ChangeNotfier` を継承しているので、 `ChangeNotifier` と同じように使うことができますが、状態変数は一つしか持ちません。

### メリット
- **シンプルな値の管理:** 状態変数が一つに強制されるので、状態管理クラスがシンプルに保たれます。そのため、単一の値の変更を管理するのに適しています。

- **簡潔な記述**: `ValueNotifier` は、 `ChangeNotifier` よりもメソッドが少なく、直感的に使用することができます。 `ChangeNotifier` には `notifyListeners` メソッドがあり、手動で呼び出す必要がありますが、 `ValueNotifier` は直接、値を更新することで変更を通知するため、`notifyListeners` の呼び忘れによるバグを防ぐことができます。

### デメリット
- **複雑な状態管理:** 複雑な状態管理が必要な場合、 `ValueNotifier` は単一の値しかサポートしていないため、十分な柔軟性がありません。

- **複数のリスナー:** `ValueNotifier` は複数のリスナーをサポートしていますが、複数のリスナーが同じイベントに対して異なる処理を実行する必要がある場合、 `ChangeNotifier` の方が適しています。

- **Providerとの統合:** `ChangeNotifier` は `Provider` パッケージとの統合が容易で、多くの状態管理ライブラリで使用されている、一方で、 `ValueNotifier` は `Provider` との統合が限定的です。

### ValueListenableBuilder
ValueListenableBuilder とは、 `ValueListenable`オブジェクト に登録された値を監視し、その値の変更に応じて、ウィジェットツリーを再構築する、Flutterで提供されているウィジェットのことです。非同期処理もサポートしており、 `Future` や `Stream` といった非同期イベントの監視にも利用できます。

#### 主なプロパティ
- **valueListenable:** `ValueListenable`のオブジェクトです。これが変更されると、 `builder` 関数が呼び出されます。
- **builder:** ウィジェットを構築するコールバック関数です。第二引数には `valueListenable` に登録されている現在の値が渡され、これに基づいてウィジェットが再構築されます。
- **child（オプション）:** オプションの `child` ウィジェットです。このプロパティは静的な部分で、再構築される必要がない場合に使用されます。

### サンプルコード
```dart
import 'package:flutter/material.dart';

class CounterModel extends ValueNotifier<int> {
  CounterModel(int value) : super(value);

  void increment() {
    value += 1;
  }
}

class MyHomePage extends StatelessWidget {
  final CounterModel counterModel;

  MyHomePage({Key? key, required this.counterModel}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ValueNotifier Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ValueListenableBuilder<int>(
              valueListenable: counterModel,
              builder: (context, value, child) {
                return Text(
                  'Counter: $value',
                  style: const TextStyle(fontSize: 24),
                );
              },
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                counterModel.increment();
              },
              child: const Text('+1'),
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      home: MyHomePage(
        counterModel: CounterModel(0),
      ),
    ),
  );
}
```
- **CounterModel クラス:**
    - `ValueNotifier<int>` を継承しているクラスです。これは `int` 型の値を保持する `ValueNotifier` として機能します。
    - `increment` メソッドで、現在の値(value)に1を加えています。

- **MyHomePage クラス:**
    - `counterModel` プロパティで、 `CounterModel` のインスタンスを受け取ります。
    - `ValueListenableBuilder` を使用して、 `counterModel` の値の変更を検知し、UIを更新しています。ボタンが押されたときに `counterModel.increment()` を呼び出して値を増加させています。

## まとめ
- **状態管理 (State Management):**
アプリケーション内でデータの変更を追跡し、それに基づいてUIを更新するプロセスのことです。Ephemeral state（ローカル状態）とApp state（共有状態）があり、UI構築において状態の整合性を保つための管理が重要になります。

- **ChangeNotifier:**
Flutterに含まれる状態管理クラスで、リスナーに変更通知を提供し、UIの更新をトリガーするクラスになります。`notifyListeners` メソッドを使用して変更を通知し、値を更新します。

- **ValueNotifier:**
`ChangeNotifier` を簡略化した状態管理クラスで、単一の値の変更を通知します。シンプルで簡潔な記述が可能で`ValueListenableBuilder` を使用して値の変更を検知し、UIを再構築します。

状態管理はアプリケーション開発において重要な概念であり、適切な方法を選択することでアプリケーションのパフォーマンスやメンテナンス性を向上させます。 `ChangeNotifier` と `ValueNotifier` はシンプルで小規模なアプリケーションに適しているため、必要に応じてより高度な状態管理パターンやライブラリを活用することが重要になってきます。

## 参考資料

https://zenn.dev/chooyan/articles/b943c6b6b0db6e

https://zenn.dev/ueshun/articles/0227f3add1b5b3

https://qiita.com/rhumie/items/f91acf30a640447c57c8

https://zenn.dev/ryouhei_furugen/scraps/f6810ccfd04177

https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction-archive/viewer/v0-change-notifier-provider

https://qiita.com/tetsufe/items/87c37f713309fb4b0e91

https://flutter.salon/widget/valuelistenablebuilder/