К содержимому

StateNotifierProvider

StateNotifierProvider - провайдер, который можно слушать, а также он хранит в себе StateNotifier (из пакета state_notifier, который Riverpod экспортирует). StateNotifierProvider с StateNotifier рекомендуется Riverpod для управления состоянием, которое может изменяться в ответ на действие пользователя.

Обычно он используется для:

  • хранения неизменяемого состояния, которое может быть изменено в ответ на определенное событие
  • расположения логики модификации состояния (бизнес логики) в одном месте, тем самым упрощая поддержку кода

В качестве примера, воспользуемся StateNotifierProvider для реализации списка задач.


// Состояние StateNotifier должно быть неизменяемым.
// Для реализации этого мы можем воспользоваться пакетами как Freezed

class Todo {
const Todo({required this.id, required this.description, required this.completed});

// В классе все поля должны быть `final`.
final String id;
final String description;
final bool completed;

// Т. к. Todo неизменяемый, создадим метод клонирования
// с возможными изменениями
Todo copyWith({String? id, String? description, bool? completed}) {
return Todo(
id: id ?? this.id,
description: description ?? this.description,
completed: completed ?? this.completed,
);
}
}

// StateNotifier, который будет передан в наш StateNotifierProvider.
// Этот класс не должен предоставлять никаких способов получения своего состояния,
// хранящегося в поле "state",
// т.е. никаких публичных геттеров/полей не должно быть!
// Публичные методы описывают то, как UI может изменять состояние.
class TodosNotifier extends StateNotifier<List<Todo>> {
// Инициализируем список задач пустым списком
TodosNotifier(): super([]);

// Добавление задач
void addTodo(Todo todo) {
// Т. к. наше состояние неизменяемо, нельзя делать подобное: `state.add(todo)`.
// Вместо этого мы должны создать новый список задач, который будет
// содержать предыдущие задачи и новую.
// Тут нам поможет spread оператор
state = [...state, todo];
// Не нужно вызывать "notifyListeners" или что-то подобное.
// Вызов "state =" автоматически перестраивает UI, когда это необходимо.
}

// Удаление задач
void removeTodo(String todoId) {
// Не забываем, что наше состояние неизменяемо.
// Так что мы создаем новый список, а не изменяем существующий.
state = [
for (final todo in state)
if (todo.id != todoId) todo,
];
}

// Изменение статуса задачи: выполнена/не выполнена
void toggle(String todoId) {
state = [
for (final todo in state)
// Изменяем статус только той задачи,
// у которой id равен todoId
if (todo.id == todoId)
// Еще раз вспомним, что наше состояние неизменяемо. Так что
// нам необходимо создавать копию списка задач.
// Воспользуемся методом `copyWith`, который мы реализовали ранее
todo.copyWith(completed: !todo.completed)
else
// Другие задачи не изменяем
todo,
];
}
}

// Используем StateNotifierProvider для взаимодействия с TodosNotifier
final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
return TodosNotifier();
});

Теперь, когда мы объявили StateNotifierProvider, мы можем использовать его для взаимодействия со списком задач:


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


Widget build(BuildContext context, WidgetRef ref) {
// перестройка виджета, когда список задач изменился
List<Todo> todos = ref.watch(todosProvider);

// Отображение задач в прокручиваемом списке
return ListView(
children: [
for (final todo in todos)
CheckboxListTile(
value: todo.completed,
// По клику меняем статус задачи
// выполнена/не выполнена
onChanged: (value) => ref.read(todosProvider.notifier).toggle(todo.id),
title: Text(todo.description),
),
],
);
}
}