Aller au contenu principal

(Async)NotifierProvider

NotifierProvider est un provider qui est utilisé pour écouter et exposer un Notifier. AsyncNotifier est un Notifier qui peut être initialisé de manière asynchrone. AsyncNotifierProvider est un provider qui est utilisé pour écouter et exposer un AsyncNotifier.
(Async)NotifierProvider avec (Async)Notifier est la solution recommandée par Riverpod pour gérer l'état qui peut changer en réaction à une interaction avec l'utilisateur.

Il est généralement utilisé pour :

  • exposer un état 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 "logique métier") en un seul endroit, ce qui améliore la maintenabilité dans le temps.

À titre d'exemple d'utilisation, nous pourrions utiliser NotifierProvider pour mettre en œuvre une liste de tâches (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 en fonction des interactions de l'utilisateur :



class Todo with _$Todo {
factory Todo({
required String id,
required String description,
required bool completed,
}) = _Todo;
}

// Ceci va générer un Notifier et un NotifierProvider.
// La classe de Notifier qui sera transmise à notre NotifierProvider.
// Cette classe ne doit pas exposer d'état en dehors de sa propriété "state", ce qui signifie que
// pas de getters/properties publics !
// Les méthodes publiques de cette classe seront celles qui permettront à l'interface utilisateur de modifier l'état.
// Enfin, nous utilisons todosProvider(NotifierProvider) pour permettre à l'interface utilisateur
// d'interagir avec notre classe Todos.

class Todos extends _$Todos {

List<Todo> build() {
return [];
}

// Permettons à l'interface utilisateur d'ajouter des todos.
void addTodo(Todo todo) {
// Comme notre état est immuable, nous ne pouvons pas faire `state.add(todo)`.
// A la place, nous devons créer une nouvelle liste de todos qui contient
// les éléments précédents et le nouveau.
// L'utilisation de l'opérateur 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 de "state ="
// reconstruira automatiquement l'interface utilisateur si nécessaire.
}

// Autorisons la suppression des todos.
void removeTodo(String todoId) {
// Encore une fois, notre état est immuable. Donc nous créons une nouvelle liste au lieu de
// changer la liste existante.
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}

// Marquons une tâche comme étant terminée.
void toggle(String todoId) {
state = [
for (final todo in state)
// nous marquons seulement le todo correspondant comme terminé
if (todo.id == todoId)
// Une fois de plus, 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 nous aider à le faire.
todo.copyWith(completed: !todo.completed)
else
// les autres todos ne sont pas modifiés
todo,
];
}
}

Maintenant que nous avons défini un NotifierProvider, nous pouvons 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) {
// reconstruit le widget lorsque la liste des tâches change.
List<Todo> todos = ref.watch(todosProvider);

// Affichons les todos dans une liste déroulante.
return ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// En appuyant sur la tâche, vous pouvez modifier son état d'achèvement.
onChanged: (value) =>
ref.read(todosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
);
}
}

À titre d'exemple d'utilisation, nous pourrions utiliser AsyncNotifierProvider pour mettre en œuvre une liste de tâches distante. Cela nous permettrait d'exposer des méthodes telles que addTodo pour permettre à l'interface utilisateur de modifier la liste des tâches en fonction des interactions de l'utilisateur :



class Todo with _$Todo {
factory Todo({
required String id,
required String description,
required bool completed,
}) = _Todo;

factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
}

// Ceci va générer un AsyncNotifier et un AsyncNotifierProvider.
// La classe AsyncNotifier qui sera transmise à notre AsyncNotifierProvider.
// Cette classe ne doit pas exposer d'état en dehors de sa propriété "state", ce qui signifie que
// pas de getters/properties publics !
// Les méthodes publiques de cette classe seront celles qui permettront à l'interface utilisateur de modifier l'état.
// Enfin, nous utilisons asyncTodosProvider(AsyncNotifierProvider) pour permettre à l'interface utilisateur
// d'interagir avec notre classe Todos.

class AsyncTodos extends _$AsyncTodos {
Future<List<Todo>> _fetchTodo() async {
final json = await http.get('api/todos');
final todos = jsonDecode(json) as List<Map<String, dynamic>>;
return todos.map(Todo.fromJson).toList();
}


FutureOr<List<Todo>> build() async {
// Chargement de la liste de tâches initiale à partir du référentiel distant
return _fetchTodo();
}

Future<void> addTodo(Todo todo) async {
// Définit l'état à "loading"
state = const AsyncValue.loading();
// Ajout du nouveau todo et rechargement de la liste des todo depuis le référentiel distant
state = await AsyncValue.guard(() async {
await http.post('api/todos', todo.toJson());
return _fetchTodo();
});
}

// Autorisons la suppression des todos.
Future<void> removeTodo(String todoId) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await http.delete('api/todos/$todoId');
return _fetchTodo();
});
}

// Marquons une tâche comme étant terminée.
Future<void> toggle(String todoId) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await http.patch(
'api/todos/$todoId',
<String, dynamic>{'completed': true},
);
return _fetchTodo();
});
}
}

Maintenant que nous avons défini un AsyncNotifierProvider, nous pouvons l'utiliser pour interagir avec la liste des todos dans notre interface utilisateur :


class TodoListView extends ConsumerWidget {
const TodoListView({super.key});


Widget build(BuildContext context, WidgetRef ref) {
// rreconstruit le widget lorsque la liste des tâches change.
final asyncTodos = ref.watch(asyncTodosProvider);

// Affichons les todos dans une liste déroulante.
return asyncTodos.when(
data: (todos) => ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// En appuyant sur la tâche, vous pouvez modifier son état d'achèvement.
onChanged: (value) =>
ref.read(asyncTodosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
),
loading: () => const Center(
child: CircularProgressIndicator(),
),
error: (err, stack) => Text('Error: $err'),
);
}
}