ChangeNotifierProvider
ChangeNotifierProvider (flutter_riverpod/hooks_riverpod のみ)は ChangeNotifier を Flutter で利用するためのプロバイダです。
Riverpod では使用を非推奨としており、主に次の理由により存在しています。
- package:providerで- ChangeNotifierProviderを利用していた場合の移行作業を容易にするため
- ミュータブル(可変)なステート管理手法をサポートするため
備考
可能な限り StateNotifierProvider を使用してください。
ChangeNotifierProvider の使用はミュータブルなステート管理を行う必然性がある場合に限定してください。
ミュータブル(可変)なステートを管理する方が都合がいいと感じるケースもあるかもしれません。
しかし、それによりコードの保守性を損ない、実装した機能が想定外の動作をするおそれがあることも念頭に置いてください。
例えば、ウィジェットの再構築を最適化するために provider.select を使っている場合、
ステートの変化がうまく select に伝わらず、再構築が機能しない可能性があります。
結果的にイミュータブルなステートを管理していた方が確実で効率が良かったと振り返ることもあるかもしれません。
そのため、ChangeNotifierProvider の導入を検討する際はユースケースに特化したベンチマークを行い、導入により全体的なパフォーマンスにどう影響するのかを慎重に見極めることが重要です。
以下のコードは Todo リストでの使用例です。まずは Todo のモデルと ChangeNotifier を定義し、ChangeNotifierProvider を作成します。
class Todo {
  Todo({
    required this.id,
    required this.description,
    required this.completed,
  });
  String id;
  String description;
  bool completed;
}
class TodosNotifier extends ChangeNotifier {
  final todos = <Todo>[];
  // UI 側から Todo アイテムを追加できるようにする
  void addTodo(Todo todo) {
    todos.add(todo);
    notifyListeners();
  }
  // Todo アイテムの削除
  void removeTodo(String todoId) {
    todos.remove(todos.firstWhere((element) => element.id == todoId));
    notifyListeners();
  }
  // Todo の完了ステータスの変更
  void toggle(String todoId) {
    for (final todo in todos) {
      if (todo.id == todoId) {
        todo.completed = !todo.completed;
        notifyListeners();
      }
    }
  }
}
// 最後に ChangeNotifierProvider を通じて UI 側から
// TodosNotifier を監視・操作できるようにする
final todosProvider = ChangeNotifierProvider<TodosNotifier>((ref) {
  return TodosNotifier();
});
次に ChangeNotifierProvider を通じて、UI 側から Todo リストを監視・操作できるようにします。
class TodoListView extends ConsumerWidget {
  const TodoListView({super.key});
  
  Widget build(BuildContext context, WidgetRef ref) {
    // Todo リストの内容に変更があればウィジェットを再構築
    List<Todo> todos = ref.watch(todosProvider).todos;
    // スクロール可能な ListView で Todo リストを表示
    return ListView(
      children: [
        for (final todo in todos)
          CheckboxListTile(
            value: todo.completed,
            // Todo をタップして完了ステータスを変更
            onChanged: (value) =>
                ref.read(todosProvider.notifier).toggle(todo.id),
            title: Text(todo.description),
          ),
      ],
    );
  }
}