Search code examples
flutterblocstate-managementcubit

State in BLoC changes its value before calling emit(), but Equatable doesn't recognize the state change


My problem is that when I'm calling a Cubit function it changes a value before I hit emit. Because of that, the Cubit cannot detect that there was a change in the state and the emit() won't work.

I solved the problem by creating a random number each time the function is called so that the cubit can recognize the state change. I just want to know what I'm doing wrong here. I'm already using the Equatable package.

part of 'survey_cubit.dart';

abstract class SurveyState extends Equatable {
  const SurveyState({
    required this.business,
    required this.locals,
    this.local = Local.en,
    this.status = BusinessStatus.setup,
    this.review = Review.empty,
    this.questionIndex = 0,
    this.answersId = const [],
    this.questionsId = const [],
    this.random = 0.0,
  });

  final Business business;
  final List<Local> locals;
  final Local local;
  final BusinessStatus status;
  final Review review;
  final int questionIndex;
  final List<int> answersId;
  final List<int> questionsId;
  final double random;

  @override
  List<Object> get props => [
        business,
        locals,
        local,
        status,
        review,
        questionIndex,
        answersId,
        questionsId,
        random,
      ];

  SurveyState copyWith({
    Business? business,
    List<Local>? locals,
    Local? local,
    BusinessStatus? status,
    Review? review,
    int? questionIndex,
    List<int>? answersId,
    List<int>? questionsId,
    double? random,
  });
}

class SurveyInitial extends SurveyState {
  const SurveyInitial({
    required super.business,
    required super.locals,
    super.local = Local.en,
    super.status = BusinessStatus.setup,
    super.review = Review.empty,
    super.questionIndex = 0,
    super.answersId = const [],
    super.questionsId = const [],
    super.random = 0.0,
  });

  @override
  SurveyState copyWith({
    Business? business,
    List<Local>? locals,
    Local? local,
    BusinessStatus? status,
    Review? review,
    int? questionIndex,
    List<int>? answersId,
    List<int>? questionsId,
    double? random,
  }) =>
      SurveyInitial(
        business: business ?? this.business,
        locals: locals ?? this.locals,
        local: local ?? this.local,
        status: status ?? this.status,
        review: review ?? this.review,
        questionIndex: questionIndex ?? this.questionIndex,
        answersId: answersId ?? this.answersId,
        questionsId: questionsId ?? this.questionsId,
        random: random ?? this.random,
      );
}

class SurveyCubit extends Cubit<SurveyState> {
  SurveyCubit(DeviceInfo deviceInfo)
      : super(
    SurveyInitial(
            business: deviceInfo.business!,
            locals: deviceInfo.locals,
          ),
        );
  void onRemoveReview(int questionId) {
    final Review review = state.review;
    review.reviewedQuestions.removeWhere(
      (element) => element.questionId == questionId,
    );
    final List<int> questionsId = state.questionsId;
    questionsId.remove(questionId);
    emit(
      state.copyWith(
        review: review,
        answersId: [],
        questionsId: questionsId,
        random: Random().nextDouble(),
      ),
    );
    print(state.questionsId);
  }
}

Solution

  • In your cubit, you assign state.questionsId (the old state's list) to a new variable. This doesn't create a new list; it just adds a new reference to the old one. The object ID is still the same. When you emit the new state, Equatable looks at the object ID and sees that it's identical and thinks the two states are the same.

    From the Bloc documentation:

    Equatable properties should always be copied rather than modified. If an Equatable class contains a List or Map as properties, be sure to use List.from or Map.from respectively to ensure that equality is evaluated based on the values of the properties rather than the reference.

    final List<int> questionsId = state.questionsId; should be final List<int> questionsId = List.from(state.questionsId);.