StateNotifierProvider
StateNotifierProvider est un provider qui est utilisé pour écouter et exposer
un StateNotifier (du package state_notifier, que Riverpod réexporte).
StateNotifierProvider avec StateNotifier est la solution recommandée par
Riverpod pour gérer l'état qui peut changer en réagissant à une interaction avec l'utilisateur.
Il est généralement utilisé pour :
- exposant un état immuable qui peut changer dans le temps après avoir réagi à des événements personnalisés.
- centraliser la logique de modification d'un état (alias "business logic") en un seul, ce qui améliore la maintenabilité au fil du temps.
A titre d'exemple d'utilisation, nous pourrions utiliser StateNotifierProvider pour implémenter une todo-list.
Cela nous permettrait d'exposer des méthodes telles que addTodo pour permettre
à l'interface utilisateur de modifier la liste des tâches lors des interactions avec
l'utilisateur.
// L'état de notre StateNotifier doit être immuable.
// On peut aussi utiliser des packages comme Freezed pour aider à la mise en œuvre.
class Todo {
  const Todo({required this.id, required this.description, required this.completed});
  // Toutes les propriétés doivent être `finales` sur notre classe.
  final String id;
  final String description;
  final bool completed;
  // Puisque Todo est immuable, on implémente une méthode qui permet de cloner le Todo 
  // avec un contenu légèrement différent.
  Todo copyWith({String? id, String? description, bool? completed}) {
    return Todo(
      id: id ?? this.id,
      description: description ?? this.description,
      completed: completed ?? this.completed,
    );
  }
}
// La classe StateNotifier qui sera transmise à notre StateNotifierProvider. 
// Cette classe ne doit pas exposer d'état (state) en dehors de sa propriété "state", 
// ce qui signifie que pas de public getteurs/properietés!
// Les méthodes publiques de cette classe seront celles qui permettront à l'interface 
// utilisateur de modifier l'état (state).
class TodosNotifier extends StateNotifier<List<Todo>> {
  // On initialise la liste des todos à une liste vide.
  TodosNotifier(): super([]);
  // Permettre à l'interface utilisateur d'ajouter des todos.
  void addTodo(Todo todo) {
    // Comme notre état est immuable, nous n'avons pas le droit de faire `state.add(todo)`.
    // Au lieu de cela, on doit créer une nouvelle liste de tâches qui contient 
    // les éléments précédents et le nouveau.
    // L'utilisation de spread de Dart est utile ici !
    state = [...state, todo];
    // Il n'est pas nécessaire d'appeler "notifyListeners" ou quelque chose de similaire. 
    // L'appel à "state =" reconstruira automatiquement l'interface utilisateur si nécessaire.
  }
  // Autorisation de supprimer les todos
  void removeTodo(String todoId) {
    // Encore une fois, notre état est immuable. 
    // Donc on crée une nouvelle liste au lieu de changer la liste existante.
    state = [
      for (final todo in state)
        if (todo.id != todoId) todo,
    ];
  }
  // Marquez une tâche comme étant terminée
  void toggle(String todoId) {
    state = [
      for (final todo in state)
        // on marque seulement le todo correspondant comme terminé
        if (todo.id == todoId)
          // Encore une fois, puisque notre état est immuable, 
          // nous devons faire une copie du todo. Nous utilisons la méthode `copyWith` 
          // implémentée précédemment pour y parvenir.
          todo.copyWith(completed: !todo.completed)
        else
          // les autres todos ne sont pas modifiés
          todo,
    ];
  }
}
// Enfin, on utilise le StateNotifierProvider pour permettre à l'interface utilisateur 
// d'interagir avec notre classe TodosNotifier.
final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
  return TodosNotifier();
});
Maintenant que nous avons défini un StateNotifierProvider, on peut l'utiliser pour
interagir avec la liste des tâches dans notre interface utilisateur :
class TodoListView extends ConsumerWidget {
  const TodoListView({super.key});
  
  Widget build(BuildContext context, WidgetRef ref) {
    // reconstruire le widget lorsque la liste des tâches change.
    List<Todo> todos = ref.watch(todosProvider);
    // Afficher (render) les todos dans une ListView.
    return ListView(
      children: [
        for (final todo in todos)
          CheckboxListTile(
            value: todo.completed,
            // En appuyant sur le todo, on modifie son état d'achèvement.
            onChanged: (value) => ref.read(todosProvider.notifier).toggle(todo.id),
            title: Text(todo.description),
          ),
      ],
    );
  }
}