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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:08:10