You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Bloc(Cubit)状态管理:如何优雅处理Todos的创建/更新状态?

解决Flutter Cubit中操作后Todo列表消失的问题

你的核心问题是:TodoCreated/TodoUpdated这类状态没有持有Todo列表数据,且BlocBuilder仅在TodoLoaded状态时渲染列表,导致状态切换后列表丢失。下面是几种简洁的处理方式:

方案一:简化状态结构,操作后直接更新并emit TodoLoaded

这是最简洁易维护的方案——移除TodoCreated/TodoUpdated这类单独的操作状态,在创建、更新、删除Todo完成后,直接获取最新的Todo列表并emitTodoLoaded状态;如果操作出错,让TodoError状态携带当前已加载的列表,确保错误时列表不会消失。

修改后的状态定义

@immutable
sealed class TodosState {}

final class TodoInitial extends TodosState {}
final class TodoLoading extends TodosState {}

final class TodoLoaded extends TodosState {
  final List<Todo> todos;

  TodoLoaded(this.todos);

  // 重写equals和hashCode,避免不必要的UI重建
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is TodoLoaded &&
          runtimeType == other.runtimeType &&
          listEquals(todos, other.todos);

  @override
  int get hashCode => todos.hashCode;
}

final class TodoError extends TodosState {
  final String message;
  final List<Todo> currentTodos; // 携带当前列表

  TodoError(this.message, this.currentTodos);
}

Cubit中的操作逻辑示例

class TodoCubit extends Cubit<TodosState> {
  final TodoRepository repository;

  TodoCubit(this.repository) : super(TodoInitial());

  // 初始加载列表
  Future<void> fetchTodos() async {
    emit(TodoLoading());
    try {
      final todos = await repository.getTodos();
      emit(TodoLoaded(todos));
    } catch (e) {
      emit(TodoError(e.toString(), []));
    }
  }

  // 创建Todo
  Future<void> createTodo(Todo newTodo) async {
    emit(TodoLoading());
    try {
      await repository.addTodo(newTodo);
      // 创建完成后重新获取最新列表
      final updatedTodos = await repository.getTodos();
      emit(TodoLoaded(updatedTodos));
      // 可在这里通过SnackBar提示创建成功
    } catch (e) {
      // 出错时保留当前列表(如果之前已加载)
      final currentTodos = state is TodoLoaded ? (state as TodoLoaded).todos : [];
      emit(TodoError(e.toString(), currentTodos));
    }
  }

  // 更新Todo同理
  Future<void> updateTodo(Todo updatedTodo) async {
    emit(TodoLoading());
    try {
      await repository.updateTodo(updatedTodo);
      final updatedTodos = await repository.getTodos();
      emit(TodoLoaded(updatedTodos));
    } catch (e) {
      final currentTodos = state is TodoLoaded ? (state as TodoLoaded).todos : [];
      emit(TodoError(e.toString(), currentTodos));
    }
  }
}

BlocBuilder中的UI处理

BlocBuilder<TodoCubit, TodosState>(
  builder: (context, state) {
    if (state is TodoInitial || state is TodoLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (state is TodoError) {
      // 显示错误信息,同时保留列表
      WidgetsBinding.instance.addPostFrameCallback((_) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(state.message), backgroundColor: Colors.red),
        );
      });
      return _buildTodoList(state.currentTodos);
    }

    if (state is TodoLoaded) {
      // 操作完成后自动展示最新列表
      return _buildTodoList(state.todos);
    }

    // 兜底返回空状态
    return const Center(child: Text('暂无数据'));
  },
)

// 抽离列表构建逻辑
Widget _buildTodoList(List<Todo> todos) {
  return ListView.builder(
    itemCount: todos.length,
    itemBuilder: (context, index) => TodoItem(todo: todos[index]),
  );
}

方案二:保留操作状态,让所有状态携带列表数据

如果不想移除TodoCreated/TodoUpdated状态,可以修改这些状态,让它们都持有当前的Todo列表,同时在BlocBuilder中统一处理所有状态的列表渲染:

修改后的状态定义

@immutable
sealed class TodosState {
  // 所有状态都包含列表(初始/加载状态可以是空列表)
  final List<Todo> todos;

  const TodosState(this.todos);
}

final class TodoInitial extends TodosState {
  const TodoInitial() : super([]);
}

final class TodoLoading extends TodosState {
  const TodoLoading(List<Todo> todos) : super(todos);
}

final class TodoLoaded extends TodosState {
  const TodoLoaded(List<Todo> todos) : super(todos);
}

final class TodoError extends TodosState {
  final String message;

  const TodoError(this.message, List<Todo> todos) : super(todos);
}

final class TodoCreated extends TodosState {
  const TodoCreated(List<Todo> todos) : super(todos);
}

final class TodoUpdated extends TodosState {
  const TodoUpdated(List<Todo> todos) : super(todos);
}

Cubit中emit状态示例

Future<void> createTodo(Todo newTodo) async {
  final currentTodos = state.todos;
  emit(TodoLoading(currentTodos));
  try {
    await repository.addTodo(newTodo);
    final updatedTodos = await repository.getTodos();
    emit(TodoCreated(updatedTodos));
  } catch (e) {
    emit(TodoError(e.toString(), currentTodos));
  }
}

BlocBuilder中统一处理

BlocBuilder<TodoCubit, TodosState>(
  builder: (context, state) {
    if (state is TodoLoading) {
      return Stack(
        children: [
          _buildTodoList(state.todos), // 加载时保留旧列表
          const Center(child: CircularProgressIndicator()),
        ],
      );
    }

    if (state is TodoError) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(state.message), backgroundColor: Colors.red),
        );
      });
    } else if (state is TodoCreated) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('创建成功')),
        );
      });
    } else if (state is TodoUpdated) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('更新成功')),
        );
      });
    }

    return _buildTodoList(state.todos);
  },
)

两种方案中,方案一更简洁易维护,推荐优先使用。

内容的提问来源于stack exchange,提问作者Michael Bervo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:24:52