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代码 ); } }
优化错误处理的方案
- 分离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()); }, ), )
- 错误状态强制传入具体信息:修改
CategoryStateError,去掉默认错误信息,强制传入具体错误内容,避免模糊错误:
class CategoryStateError extends CategoriesState { final String errorMessage; CategoryStateError({required this.errorMessage}); }
- 操作完成后直接发射加载状态:在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
相关产品推荐
相关产品推荐

