🐳

[Flutter / Riverpod] StateNotifierProviderが推奨される理由と、Todoアプリのサンプル

2022/09/18に公開

はじめに

個人的に状態管理にはChangeNotifierProviderを使用していましたが、Riverpodのドキュメントには下記のような記述が。

可能な限り StateNotifierProvider を使用してください。
ChangeNotifierProvider の使用はミュータブルなステート管理を行う必然性がある場合に限定してください。

特別な理由がない限り、Modelクラスはimmutable[1]であったほうが予期せぬ動作を防ぐことができるため、
状態管理にもChangeNotifierではなくStateNotifierを使用した方が無難だったようです。

参考

https://riverpod.dev/ja/docs/providers/state_notifier_provider

上記ドキュメントを参考に、Todoアプリのサンプル作成の手順を記します。

手順

  1. パッケージインストール
  • state_notifier[2]
  • flutter_hooks[3]
  • hooks_riverpod[4]
$ flutter pub add state_notifier
$ flutter pub add flutter_hooks
$ flutter pub add hooks_riverpod
  1. コード
  • Modelクラス
    Freezedでの生成も可[5]
todo.dart
// ドキュメントから引用(copyWithメソッドは今回使わないため除外)

class Todo {
  const Todo({required this.id, required this.description});
  final String id;
  final String description;
}
  • ViewModelクラス
todo_view_model.dart
class TodosNotifier extends StateNotifier<List<Todo>> {
  TodosNotifier(): super([
    // 初期値として適当なデータを入れています
    Todo(id: '0', description: 'メモ0'),
    Todo(id: '1', description: 'メモ1'),
    Todo(id: '2', description: 'メモ2'),
  ]);

  void addTodo(Todo todo) {
    // stateのみ変更の処理をしているが、DBの操作もここで追記必要
    state = [...state, todo];
  }

  void removeTodo(String todoId) {
    // stateのみ変更の処理をしているが、DBの操作もここで追記必要
    state = [
      for (final todo in state)
        if (todo.id != todoId) todo,
    ];
  }

}

final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
  return TodosNotifier();
});
  • Viewクラス
todo_page.dart
class TodoPage extends ConsumerWidget {
  const TodoPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    List<Todo> todos = ref.watch(todosProvider);
    return Scaffold(
      appBar: AppBar(
        title: Text('TODO'),
      ),
      body: SingleChildScrollView(
        child: ListView(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          children: [
            for (final todo in todos)
              ListTile(
                title: Text(todo.description),
                trailing: IconButton(
                  icon: Icon(
                    Icons.delete,
                    color: Colors.red,
                  ),
                  onPressed: () {
                    ref.read(todosProvider.notifier).removeTodo(todo.id);
                  },
                ),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          ref.read(todosProvider.notifier).addTodo(
            // 適当なデータを渡しています
            Todo(
                id: '${ref.read(todosProvider.notifier).state.length}',
                description:
                'メモ${ref.read(todosProvider.notifier).state.length}'
            ),
          );
        },
      ),
    );
  }
}

おわりに

まず完成イメージ

Riverpodでの状態管理を知った時は、StateNotifier?、ChangeNotifier?って感じでしたが、
少しメリットが理解できてきました。
ModelクラスをFreezedで生成する方法について別記事に載せているので、参考にしていただけますと幸いです。
https://zenn.dev/taku_zenn/articles/d5134fbd1540ee

脚注
  1. 脚注の内容その1
    https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/chapter7_immutable ↩︎

  2. 脚注の内容その2
    https://pub.dev/packages/state_notifier ↩︎

  3. 脚注の内容その3
    https://pub.dev/packages/flutter_hooks/install ↩︎

  4. 脚注の内容その4
    https://pub.dev/packages/hooks_riverpod/install ↩︎

  5. 脚注の内容その5
    https://zenn.dev/taku_zenn/articles/d5134fbd1540ee ↩︎

Discussion