(Async)NotifierProvider
NotifierProvider 是一个监听和暴露 Notifier 的provider。
AsyncNotifier 是一个可以异步初始化的 Notifier。
AsyncNotifierProvider 是一个用于监听和公开 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()),
    };
  }
}