Flutter Bloc向Hive Box添加商品时状态无法即时更新排查
问题:Flutter Bloc + Hive 购物车状态无法即时更新
使用Bloc做状态管理、Hive做本地存储的Flutter应用中,向购物车添加商品后,Bloc无法即时触发UI更新,只有切换页面或热重启后才生效。已尝试用List.from创建新列表强制触发状态变更,但无效;同时CartLoadingState也没有被发射出来。
CartBloc 代码
class CartBloc extends Bloc<CartEvent, CartState> { final Box cartBox; CartBloc(this.cartBox) : super(CartInitialState()) { on<AddItemEvent>((event, emit) async { emit(CartLoadingState()); print('AddItemEvent started'); final existingItem = cartBox.values.firstWhere( (item) => (item as Map)['id_curso'] == event.courseItem.id, orElse: () => null, ); if (existingItem == null) { final itemToAdd = event.courseItem.toMap() ..['tipo_certificado'] = event.certificateType == 'digital' ? '65' : '64'; await cartBox.add(itemToAdd); print('Item added to cartBox'); } else { print('Item already exists in cartBox'); } final updatedCartItems = _getCartItems(); emit(CartSuccessState(List.from(updatedCartItems), _calculateTotalPrice(updatedCartItems))); print('CartSuccessState emitted'); }); on<RemoveItemEvent>((event, emit) async { emit(CartLoadingState()); print('RemoveItemEvent started'); final key = cartBox.keys.firstWhere( (k) => (cartBox.get(k) as Map)['id_curso'] == event.id, orElse: () => null, ); if (key != null) { await cartBox.delete(key); print('Item removed from cartBox'); } else { print('Item not found in cartBox'); } final updatedCartItems = _getCartItems(); emit(CartSuccessState(List.from(updatedCartItems), _calculateTotalPrice(updatedCartItems))); print('CartSuccessState emitted'); }); on<ClearCartEvent>((event, emit) async { emit(CartLoadingState()); print('ClearCartEvent started'); await cartBox.clear(); print('All items removed from cartBox'); final updatedCartItems = _getCartItems(); emit(CartSuccessState(List.from(updatedCartItems), _calculateTotalPrice(updatedCartItems))); print('CartSuccessState emitted'); }); on<GetCartItemsEvent>((event, emit) { emit(CartLoadingState()); print('GetCartItemsEvent started'); final updatedCartItems = _getCartItems(); emit(CartSuccessState(List.from(updatedCartItems), _calculateTotalPrice(updatedCartItems))); print('CartSuccessState emitted'); }); } List<UserCourseModel> _getCartItems() { return cartBox.values.map((item) { final map = Map<String, dynamic>.from(item as Map); return UserCourseModel.fromMap(map); }).toList(); } double _calculateTotalPrice(List<UserCourseModel> cartItems) { return cartItems.fold(0.0, (sum, item) => sum + double.parse(item.certificatePrice.replaceAll(',', '.'))); } }
UI触发代码
onTap: () { cartBloc.add(AddItemEvent(course, 'printed')); Navigator.of(context).pop(); },
CartState 代码
abstract class CartState extends Equatable {} class CartInitialState implements CartState { @override List<Object?> get props => []; @override bool get stringify => false; } class CartLoadingState implements CartState { @override List<Object?> get props => []; @override bool get stringify => false; } class CartSuccessState implements CartState { final List<UserCourseModel> cartItems; final double totalPrice; CartSuccessState(this.cartItems, this.totalPrice); @override List<Object?> get props => [cartItems, totalPrice]; @override bool get stringify => false; } class CartErrorState implements CartState { final String message; const CartErrorState(this.message); @override List<Object?> get props => [message]; @override bool get stringify => false; }
CartEvent 代码
abstract class CartEvent {} class AddItemEvent extends CartEvent { final UserCourseModel courseItem; final String certificateType; AddItemEvent(this.courseItem, this.certificateType); } class RemoveItemEvent extends CartEvent { final String id; RemoveItemEvent(this.id); } class ClearCartEvent extends CartEvent {} class GetCartItemsEvent extends CartEvent {}
UserCourseModel 代码
class UserCourseModel { final String toId; final String id; final String userId; final String status; final String name; final String imgUrl; final String certificatePrice; final String digitalCertificatePrice; final String initDate; final String? conclusionDate; final String? certificateType; final List<ModuleModel> modules; UserCourseModel({ required this.toId, required this.id, required this.userId, required this.status, required this.name, required this.imgUrl, required this.certificatePrice, required this.digitalCertificatePrice, required this.initDate, this.conclusionDate, this.certificateType, required this.modules, }); factory UserCourseModel.fromMap(Map<String, dynamic> map) { return UserCourseModel( toId: map['id_to'] as String, id: map['id_curso'] as String, userId: map['id_usuario'] as String, status: map['status_curso'] as String, name: map['nome_curso'] as String, imgUrl: map['img_url'] as String, certificatePrice: map['preco_certificado'] as String, digitalCertificatePrice: map['preco_certificado_digital'] as String, initDate: map['data_inicio_curso'] as String, conclusionDate: map['data_conclusao_curso'] as String? ?? '', certificateType: map['tipo_certificado'] as String? ?? '', modules: (map['curso']['lista_modulos'] as List) .map((item) => ModuleModel.fromMap(Map<String, dynamic>.from(item))) .toList(), ); } Map<String, dynamic> toMap() { return { 'id_to': toId, 'id_curso': id, 'id_usuario': userId, 'status_curso': status, 'nome_curso': name, 'img_url': imgUrl, 'preco_certificado': certificatePrice, 'preco_certificado_digital': digitalCertificatePrice, 'data_inicio_curso': initDate, 'data_conclusao_curso': conclusionDate ?? '', 'tipo_certificado': certificateType ?? '', 'curso': { 'lista_modulos': modules.map((module) => module.toMap()).toList(), }, }; } } class ModuleModel { final String id; final String name; final String totalTopics; final List<TopicModel> topics; ModuleModel({ required this.id, required this.name, required this.totalTopics, required this.topics, }); factory ModuleModel.fromMap(Map<String, dynamic> map) { return ModuleModel( id: map['id_modulo'] as String, name: map['nome_modulo'] as String, totalTopics: map['total_topicos'] as String, topics: (map['lista_topicos'] as List) .map((item) => TopicModel.fromMap(Map<String, dynamic>.from(item))) .toList(), ); } Map<String, dynamic> toMap() { return { 'id_modulo': id, 'nome_modulo': name, 'total_topicos': totalTopics, 'lista_topicos': topics.map((topic) => topic.toMap()).toList(), }; } } class TopicModel { final String orderPosition; final String id; final String moduleId; final String title; TopicModel({ required this.orderPosition, required this.id, required this.moduleId, required this.title, }); factory TopicModel.fromMap(Map<String, dynamic> map) { return TopicModel( orderPosition: map['ordem_posicao'] as String, id: map['id_topico'] as String, moduleId: map['id_modulo'] as String, title: map['titulo_topico'] as String, ); } Map<String, dynamic> toMap() { return { 'ordem_posicao': orderPosition, 'id_topico': id, 'id_modulo': moduleId, 'titulo_topico': title, }; } }
问题原因分析
1. 状态类未正确继承Equatable
CartState声明继承自Equatable,但所有具体状态类都用implements而非extends,导致Equatable的相等性判断逻辑失效。Bloc依赖Equatable的props判断新旧状态是否相同,若状态类未正确继承,即使创建新列表,Bloc仍可能认为新旧状态相等,不发射新状态。
2. 数据模型无相等性判断
CartSuccessState的props包含cartItems列表,但列表中的UserCourseModel、ModuleModel、TopicModel未重写==和hashCode,也未继承Equatable。即使列表是新的,元素引用相同,Equatable递归比较时会判定状态相等,Bloc不触发更新。
3. CartLoadingState未被感知的原因
- UI触发事件后立刻调用
Navigator.pop(),当前页面的BlocBuilder可能在CartLoadingState发射前就已销毁,无法接收状态。 - 若BlocBuilder未处理
CartLoadingState,或该状态与前序状态被判定为相等,Bloc会跳过发射。
修复方案
1. 修正状态类继承关系
让所有具体状态类继承CartState,确保Equatable逻辑生效:
abstract class CartState extends Equatable { const CartState(); } class CartInitialState extends CartState { @override List<Object?> get props => []; } class CartLoadingState extends CartState { @override List<Object?> get props => []; } class CartSuccessState extends CartState { final List<UserCourseModel> cartItems; final double totalPrice; const CartSuccessState(this.cartItems, this.totalPrice); @override List<Object?> get props => [cartItems, totalPrice]; } class CartErrorState extends CartState { final String message; const CartErrorState(this.message); @override List<Object?> get props => [message]; }
2. 让数据模型继承Equatable
为所有模型类添加Equatable继承并重写props:
class UserCourseModel extends Equatable { // 原有字段... @override List<Object?> get props => [ toId, id, userId, status, name, imgUrl, certificatePrice, digitalCertificatePrice, initDate, conclusionDate, certificateType, modules ]; } class ModuleModel extends Equatable { // 原有字段... @override List<Object?> get props => [id, name, totalTopics, topics]; } class TopicModel extends Equatable { // 原有字段... @override List<Object?> get props => [orderPosition, id, moduleId, title]; }
3. 调整UI触发逻辑
等待事件处理完成后再pop页面,确保状态更新被接收:
onTap: () async { await cartBloc.add(AddItemEvent(course, 'printed')); Navigator.of(context).pop(); },
4. 完善BlocBuilder状态处理
在UI中处理CartLoadingState,确保所有状态都被监听:
BlocBuilder<CartBloc, CartState>( builder: (context, state) { if (state is CartLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is CartSuccessState) { // 渲染购物车列表 return ListView.builder(...); } return const SizedBox(); }, )
内容的提问来源于stack exchange,提问作者Leonardo Maito
相关产品推荐
相关产品推荐

