Flutter Bloc неправильно обновляет состояние при добавлении элементов в Hive Box

Я работаю над приложением Flutter, где использую Bloc для управления состоянием и Hive для локального хранилища. Я столкнулся с проблемой, когда состояние не обновляется правильно, когда я добавляю товар в корзину. В частности, когда я добавляю элемент в Hive Box, Блок, похоже, не сразу распознает изменение состояния.

Он распознает изменение, когда я меняю страницу или перезапускаю приложение.

Я добавил List.from, чтобы попытаться создать новый список для принудительного изменения состояния, поскольку, насколько я понимаю, Bloc не будет генерировать новое состояние, если оно идентично последнему.

Если это помогает, похоже, он также не генерирует событие 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(',', '.')));
  }
}

А вот фрагмент кода пользовательского интерфейса, который запускает AddItemEvent:

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,
    };
  }
}

показать модель пользовательского курса

Vladyslav Ulianytskyi 17.06.2024 20:06

Добавлено по запросу.

Leonardo Maito 17.06.2024 21:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как вы сказали: «Блок не будет генерировать новое состояние, если оно идентично предыдущему».

И велика вероятность, что причина именно в этом. Ваши модели UserCourseModel, TopicModel, ModuleModel идентичны по испускаемым состояниям. Чтобы избежать этого, вам следует переопределить равенства и хэш-код для каждой из них. Обычной практикой, позволяющей не делать это вручную для каждой модели, является использование таких пакетов, как equatable , Frosed и т. д.

Следование вашему совету, а также изменение моего CartBloc с Factory на Singleton в моих зависимостях GetIt решило проблему отсутствия обновления корзины в реальном времени. Спасибо.

Leonardo Maito 18.06.2024 16:56

да, я точно пропустил момент создания блока

Vladyslav Ulianytskyi 18.06.2024 17:31

Вы не виноваты, я не написал это, предполагая, что это правильно. Виноват.

Leonardo Maito 18.06.2024 17:48

Другие вопросы по теме