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;
}

// This will generates a Notifier and NotifierProvider.
// The Notifier class that will be passed to our NotifierProvider.
// This class should not expose state outside of its "state" property, which means
// no public getters/properties!
// The public methods on this class will be what allow the UI to modify the state.
// Finally, we are using todosProvider(NotifierProvider) to allow the UI to
// interact with our Todos class.

class Todos extends _$Todos {

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

// Let's allow the UI to add todos.
void addTodo(Todo todo) {
// Since our state is immutable, we are not allowed to do `state.add(todo)`.
// Instead, we should create a new list of todos which contains the previous
// items and the new one.
// Using Dart's spread operator here is helpful!
state = [...state, todo];
// No need to call "notifyListeners" or anything similar. Calling "state ="
// will automatically rebuild the UI when necessary.
}

// Let's allow removing todos
void removeTodo(String todoId) {
// Again, our state is immutable. So we're making a new list instead of
// changing the existing list.
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}

// Let's mark a todo as completed
void toggle(String todoId) {
state = [
for (final todo in state)
// we're marking only the matching todo as completed
if (todo.id == todoId)
// Once more, since our state is immutable, we need to make a copy
// of the todo. We're using our `copyWith` method implemented before
// to help with that.
todo.copyWith(completed: !todo.completed)
else
// other todos are not modified
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) {
// rebuild the widget when the todo list changes
List<Todo> todos = ref.watch(todosProvider);

// Let's render the todos in a scrollable list view
return ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// When tapping on the todo, change its completed status
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);
}

// This will generates a AsyncNotifier and AsyncNotifierProvider.
// The AsyncNotifier class that will be passed to our AsyncNotifierProvider.
// This class should not expose state outside of its "state" property, which means
// no public getters/properties!
// The public methods on this class will be what allow the UI to modify the state.
// Finally, we are using asyncTodosProvider(AsyncNotifierProvider) to allow the UI to
// interact with our Todos class.

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 {
// Load initial todo list from the remote repository
return _fetchTodo();
}

Future<void> addTodo(Todo todo) async {
// Set the state to loading
state = const AsyncValue.loading();
// Add the new todo and reload the todo list from the remote repository
state = await AsyncValue.guard(() async {
await http.post('api/todos', todo.toJson());
return _fetchTodo();
});
}

// Let's allow removing todos
Future<void> removeTodo(String todoId) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await http.delete('api/todos/$todoId');
return _fetchTodo();
});
}

// Let's mark a todo as completed
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) {
// rebuild the widget when the todo list changes
final asyncTodos = ref.watch(asyncTodosProvider);

// Let's render the todos in a scrollable list view
return switch (asyncTodos) {
AsyncData(:final value) => ListView(
children: [
for (final todo in value)
CheckboxListTile(
value: todo.completed,
// When tapping on the todo, change its completed status
onChanged: (value) {
ref.read(asyncTodosProvider.notifier).toggle(todo.id);
},
title: Text(todo.description),
),
],
),
AsyncError(:final error) => Text('Error: $error'),
_ => const Center(child: CircularProgressIndicator()),
};
}
}