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

Flutter Bloc-Cubit:弹窗操作时如何保留页面数据及优化错误处理

解决Flutter Cubit状态切换导致页面空白的问题

问题根源

当前状态设计中,AddingCategory、CategoryAdded、CategoryStateError等状态未携带已加载的分类数据,当Cubit发射这些状态时,页面的BlocBuilder因判断state is! CategoriesLoaded会直接显示加载圈,导致原有数据消失。

解决方案一:调整状态结构,让所有状态携带数据

将基础状态抽象为包含分类数据的结构,让操作中、错误状态都继承该基础状态,确保任何状态下都能获取已加载的数据。

修改状态定义

part of 'categories_cubit.dart';

abstract class CategoriesState {
  final List<Category>? categories;
  final List<Category>? filteredData;
  final int? sortIndex;
  final bool? sortAscending;

  CategoriesState({
    this.categories,
    this.filteredData,
    this.sortIndex,
    this.sortAscending,
  });
}

class CategoriesInitial extends CategoriesState {}

class CategoriesLoaded extends CategoriesState {
  CategoriesLoaded({
    required super.categories,
    super.filteredData,
    super.sortIndex,
    required super.sortAscending,
  });
}

// 添加操作相关状态
class AddingCategory extends CategoriesState {
  AddingCategory({
    required super.categories,
    super.filteredData,
    super.sortIndex,
    required super.sortAscending,
  });
}

class CategoryAdded extends CategoriesState {
  CategoryAdded({
    required super.categories,
    super.filteredData,
    super.sortIndex,
    required super.sortAscending,
  });
}

// 删除操作相关状态(同理修改更新状态)
class DeletingCategory extends CategoriesState {
  DeletingCategory({
    required super.categories,
    super.filteredData,
    super.sortIndex,
    required super.sortAscending,
  });
}

class CategoryDeleted extends CategoriesState {
  CategoryDeleted({
    required super.categories,
    super.filteredData,
    super.sortIndex,
    required super.sortAscending,
  });
}

// 错误状态
class CategoryStateError extends CategoriesState {
  final String errorMessage;

  CategoryStateError({
    required this.errorMessage,
    required super.categories,
    super.filteredData,
    super.sortIndex,
    required super.sortAscending,
  });
}

修改Cubit发射状态的逻辑

执行操作时携带当前分类数据发射状态,确保状态切换后仍有数据可用:

Future<void> addCategory(Category category) async {
  final currentState = state;
  if (currentState is! CategoriesLoaded) return;

  // 发射添加中状态,携带当前数据
  emit(AddingCategory(
    categories: currentState.categories,
    filteredData: currentState.filteredData,
    sortIndex: currentState.sortIndex,
    sortAscending: currentState.sortAscending,
  ));

  try {
    await repository.addCategory(category);
    // 获取更新后的分类列表,发射加载完成状态
    final updatedCategories = await repository.getCategories();
    emit(CategoriesLoaded(
      categories: updatedCategories,
      filteredData: updatedCategories,
      sortIndex: currentState.sortIndex,
      sortAscending: currentState.sortAscending,
    ));
    Navigator.of(context).pop(); // 关闭弹窗
  } catch (e) {
    // 发射错误状态,携带当前数据
    emit(CategoryStateError(
      errorMessage: e.toString(),
      categories: currentState.categories,
      filteredData: currentState.filteredData,
      sortIndex: currentState.sortIndex,
      sortAscending: currentState.sortAscending,
    ));
    // 显示错误提示
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
  }
}

修改页面BlocBuilder逻辑

基于状态中的数据渲染UI,同时根据状态类型显示加载遮罩或错误提示:

BlocBuilder<CategoriesCubit, CategoriesState>(
  builder: (context, state) {
    if (state is CategoriesInitial) {
      context.read<CategoriesCubit>().fetchCategories();
      return const Center(child: CircularProgressIndicator());
    }

    // 提取状态中的数据,无数据则用默认值
    final categories = state.categories ?? [];
    final filteredData = state.filteredData ?? categories;
    final sortIndex = state.sortIndex;
    final sortAscending = state.sortAscending ?? true;

    return Stack(
      children: [
        // 原有DataTable UI,使用提取的数据
        Container(
          child: DataTable(
            columns: [...],
            rows: filteredData.map((cat) => DataRow(...)).toList(),
          ),
        ),
        // 操作中状态显示加载遮罩
        if (state is AddingCategory || state is DeletingCategory || state is UpdatingCategory)
          Container(
            color: Colors.black54,
            child: const Center(child: CircularProgressIndicator()),
          ),
        // 错误状态显示顶部提示
        if (state is CategoryStateError)
          Positioned(
            top: 0,
            left: 0,
            right: 0,
            child: Container(
              color: Colors.redAccent,
              padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
              child: Text(
                state.errorMessage,
                style: const TextStyle(color: Colors.white, fontSize: 14),
              ),
            ),
          ),
      ],
    );
  },
)

解决方案二:保留上一次有效状态,无需修改状态结构

在页面State中保存最后一次的CategoriesLoaded状态,当状态切换到非加载状态时,使用保存的数据渲染UI。

修改页面代码

class _CategoryPageState extends State<CategoryPage> {
  final searchController = TextEditingController();
  CategoriesLoaded? _lastLoadedState;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 1500,
      height: 1000,
      child: BlocBuilder<CategoriesCubit, CategoriesState>(
        builder: (context, state) {
          // 每次加载完成时保存状态
          if (state is CategoriesLoaded) {
            _lastLoadedState = state;
          }

          if (state is CategoriesInitial) {
            context.read<CategoriesCubit>().fetchCategories();
            return const Center(child: CircularProgressIndicator());
          }

          // 如果有保存的有效状态,就用它渲染
          if (_lastLoadedState != null) {
            return Stack(
              children: [
                _buildDataTable(_lastLoadedState!),
                // 操作中显示加载圈
                if (state is AddingCategory || state is DeletingCategory || state is UpdatingCategory)
                  const Center(child: CircularProgressIndicator()),
                // 错误状态显示提示
                if (state is CategoryStateError)
                  Positioned(
                    top: 20,
                    left: 0,
                    right: 0,
                    child: Container(
                      color: Colors.red,
                      padding: const EdgeInsets.all(16),
                      child: Text(
                        state.errorMessage ?? "Error encountered",
                        style: const TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
              ],
            );
          }

          return const Center(child: CircularProgressIndicator());
        },
      ),
    );
  }

  // 提取DataTable渲染逻辑
  Widget _buildDataTable(CategoriesLoaded state) {
    return Container(
      // 原有DataTable代码
    );
  }
}

优化错误处理的方案

  1. 分离UI渲染与副作用处理:使用BlocListener监听操作完成、错误状态,单独处理弹窗关闭、错误提示等逻辑,让BlocBuilder专注于数据渲染:
BlocListener<CategoriesCubit, CategoriesState>(
  listener: (context, state) {
    if (state is CategoryAdded || state is CategoryDeleted || state is CategoryUpdated) {
      Navigator.of(context).pop();
      // 重新加载最新数据
      context.read<CategoriesCubit>().fetchCategories();
    } else if (state is CategoryStateError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.errorMessage ?? "Error encountered")),
      );
    }
  },
  child: BlocBuilder<CategoriesCubit, CategoriesState>(
    builder: (context, state) {
      // 仅处理数据渲染逻辑
      if (state is CategoriesInitial) {
        context.read<CategoriesCubit>().fetchCategories();
        return const Center(child: CircularProgressIndicator());
      }
      if (state is CategoriesLoaded) {
        return _buildDataTable(state);
      }
      // 非加载状态时,尝试使用上一次的有效数据
      final lastState = context.read<CategoriesCubit>().state;
      return lastState is CategoriesLoaded ? _buildDataTable(lastState) : const Center(child: CircularProgressIndicator());
    },
  ),
)
  1. 错误状态强制传入具体信息:修改CategoryStateError,去掉默认错误信息,强制传入具体错误内容,避免模糊错误:
class CategoryStateError extends CategoriesState {
  final String errorMessage;

  CategoryStateError({required this.errorMessage});
}
  1. 操作完成后直接发射加载状态:在Cubit中操作成功后,直接发射CategoriesLoaded状态(携带更新后的数据),减少冗余状态类型:
Future<void> addCategory(Category category) async {
  final currentState = state;
  if (currentState is! CategoriesLoaded) return;

  emit(AddingCategory(
    categories: currentState.categories,
    filteredData: currentState.filteredData,
    sortIndex: currentState.sortIndex,
    sortAscending: currentState.sortAscending,
  ));

  try {
    await repository.addCategory(category);
    final updatedCategories = await repository.getCategories();
    // 直接发射加载完成状态,无需单独的CategoryAdded
    emit(CategoriesLoaded(
      categories: updatedCategories,
      filteredData: updatedCategories,
      sortIndex: currentState.sortIndex,
      sortAscending: currentState.sortAscending,
    ));
    Navigator.of(context).pop();
  } catch (e) {
    emit(CategoryStateError(
      errorMessage: e.toString(),
      categories: currentState.categories,
      filteredData: currentState.filteredData,
      sortIndex: currentState.sortIndex,
      sortAscending: currentState.sortAscending,
    ));
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 02:45:43