Aller au contenu principal

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.
@immutable
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({Key? key}): super(key: key);

@override
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),
),
],
);
}
}