Chapter 08

[v0.14.0以下版] StateNotifierProviderで状態を購読・複雑に操作する

村松龍之介
村松龍之介
2023.02.12に更新

StateNotifierProvider - 状態の公開と高度な状態操作

状態を格納し、監視できるStateNorifierを使用して、状態を公開・操作できるProvider。

StateProviderとの大きな違いは、単純な状態の保持だけでなく、状態を操作するメソッドをStateNotifierを継承したクラスに定義できること。

StateProviderを使った場合に、Widgetが状態操作のロジックで溢れてしまう場合もStateNotifierの使用を検討しましょう。

StateNotifierを継承したクラスを作成

Providerの作成の前に、必要なクラスを作成しましょう。

TODOのリストを State(状態)に持ち、TODOを操作するクラスの例です。

todoListNotifier.dart
class TodoListNotifier extends StateNotifier<List<Todo>> {
  // `super([])` で、空のTODOリストを初期値として入れている。
  const TodoListNotifier(): super([]);

  /// 新しいTODOを追加するメソッド
  void add(Todo todo) {
    state = [...state, todo];
  }

  /// IDを指定して、TODOを削除するメソッド
  void remove(String todoId) {
    state = [
      for (final todo in state)
        if (todo.id != todoId) todo,
    ];
  }

  /// IDを指定して、TODOの完了状態を切り替えるメソッド
  void toggle(String todoId) {
    state = [
      for (final todo in state)
        if (todo.id == todoId)
          todo.copyWith(completed: !todo.completed),
    ];
  }
}

Providerの宣言

作成した TodoListNotifier のProviderを宣言します。

専用のファイルを作成して書いても良いですし、 class TodoListNotifier の上に書いても良いと思います。

todoListNotifier.dart
// Providerの定数をグローバルに宣言
// StateNotifierProviderの後に続けて、Notifierクラスの型と、格納する状態の型を明示する
final todoListProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>(
  (ref) => TodoListNotifier(),
);

Widgetで使用する

todoListPage.dart

Widget build(BuildContext context, ScopedReader watch) {
  // StateNotifierProviderを読み取る。watchを使用しているので、
  // state(状態)であるTODOリストが更新されると、buildメソッドが再実行されて画面が更新される
  final todoList = watch(todoListProvider);
  // TodoListNotifier を使用する場合は `.notifier` を付けてProviderを読み取る
  final notifier = watch(todoListProvider.notifier);
  return Scaffold(
    body: ListView(
      children: [
        for (final todo in todoList)
          ListTile(
            // TODOのタイトルをTextで表示
            title: Text(todo.title),
            // タップでTODOの完了状態を切り替える
            onTap: () => notifier.toggle(todo.id),
          )
      ],
    ),
  );
}

もう一つの書き方

buildメソッド内ではなく、ボタンを押した時などに Notifier を読み取って使用する記法として以下のようにも書けます。

onTap: () => context.read(todoListProvider.notifier).toggle(todo.id),

詳しくは、「context.readでwatchせずにProviderを利用する」の章で解説します。


参考リンク

StateNotifierProvider | Riverpodドキュメントページ
https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html