跳到主要内容

(Async)NotifierProvider

NotifierProvider 是一个监听和暴露 Notifier 的provider。 AsyncNotifier 是一个可以异步初始化的 NotifierAsyncNotifierProvider 是一个用于监听和公开 AsyncNotifier 的provider。 (Async)NotifierProvider(Async)Notifier 是Riverpod推荐的管理状态的方案, 这些状态可能会因用户交互而发生变化。

它一般用于:

  • 暴露在对自定义事件做出反应后可以随时间推移变化的状态。
  • 修改某些状态的逻辑(又名“业务逻辑”)集中在一个地方,随着时间的推移也能提高可维护性。

作为使用示例,我们使用 NotifierProvider 来实现一个待办清单。 这样做将允许我们公开 addTodo 等方法,让UI在用户交互时修改待办清单的列表:



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

// 这会生成一个Notifier 和 NotifierProvider。
// Notifier类将会被传递给我们的NotifierProvider。
// 这个类不应该在其“state”属性之外暴露状态,也就是说没有公共的获取属性的方法!
// 这个类上的公共方法将允许UI修改它的状态。
// 最后我们使用todosProvider(NotifierProvider)来允许UI与我们的Todos类进行交互。

class Todos extends _$Todos {

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

// 让我们添加UI添加待办清单
void addTodo(Todo todo) {
// 由于状态是不可变的,因此不允许执行 `state.add(todo)`。
// 相反,我们应该创建一个包含以前的项目和新的项目的待办清单列表。
// 在这里使用Dart的扩展运算符很有用!
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)
// 我们只标记完成的待办清单
if (todo.id == todoId)
// 再一次因为我们的状态是不可变的,所以我们需要创建待办清单的副本,
// 我们使用之前实现的copyWith方法来实现。
todo.copyWith(completed: !todo.completed)
else
// 其他未修改的待办清单
todo,
];
}
}

现在我们定义了一个 NotifierProvider ,我们可以使用它来与UI中的待办清单列表交互:


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

下面的使用示例,我们可以使用 AsyncNotifierProvider 来实现一个远程待办清单列表。 这样做将允许我们暴露 addTodo 等方法,让UI在用户交互时修改待办清单列表:



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

// 这会生成一个 AsyncNotifier 和 AsyncNotifierProvider。
// Notifier类将会被传递给我们的 AsyncNotifierProvider。
// 这个类不应该在其“state”属性之外暴露状态,也就是说没有公共的获取属性的方法!
// 这个类上的公共方法将允许UI修改它的状态。
// 最后我们使用asyncTodosProvider(AsyncNotifierProvider)来允许UI与我们的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 {
// 从远程仓库获取初始的待办清单
return _fetchTodo();
}

Future<void> addTodo(Todo todo) async {
// 将当前状态设置为加载中
state = const AsyncValue.loading();
// 将新的待办清单添加到远程仓库
state = await AsyncValue.guard(() async {
await http.post('api/todos', todo.toJson());
return _fetchTodo();
});
}

// 让我们允许删除待办清单
Future<void> removeTodo(String todoId) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await http.delete('api/todos/$todoId');
return _fetchTodo();
});
}

// 让我们把待办清单标记为已完成
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();
});
}
}

现在我们已经定义了一个 AsyncNotifierProvider ,我们可以使用它来与UI中的待办清单交互:


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()),
};
}
}