Search code examples
flutterbloc

Widgets in a list don't react to change while using bloc pattern in Flutter


Here I have a _buildFrontCardDisplaySetting which is used to build a list of checkbox to determine which field should be displayed. Each widget in a list is wrapped by a BlocBuilder, and in the widget is a CheckBoxListTile. In the CheckBoxListTile, its value will set to true if the setting option, which is bound to the checkbox, in the list setting in the state class. My problem is: the UI doesn't react to change if I click the checkbox. I have debugged the code, and it does jump to the copyWith method in the class state, but the bloc class doesn't emit any state. I am struggling to find the root cause for my issue. If anyone have an idea here?. Below is my video screenshot and the code:

My video screenshot:

enter image description here

My widget:

 @override
  Widget build(BuildContext context) {
    return BlocListener<FlashCardSettingCubit, FlashCardSettingState>(
      listener: (context, state) {
        ...
      },
      child: SimpleDialog(
        children: [
          ..._buildFrontCardDisplaySetting(context),
        ],
      ),
    );
};


 List<Widget> _buildFrontCardDisplaySetting(BuildContext context) {
    final list = FrontSideDisplaySetting.values.map((e) {
      return BlocBuilder<FlashCardSettingCubit, FlashCardSettingState>(
        buildWhen: (prev, current){
          return prev.frontSidesSetting != current.frontSidesSetting;
        },
        builder: (context, state) {
          return CheckboxListTile(
              key: ValueKey(e),
              title: Text(context.frontSetting(e)),
              value: state.frontSidesSetting.contains(e),
              onChanged: (value) {
                context.read<FlashCardSettingCubit>().chooseFrontCardSetting(e);
              });
        },
      );
    }).toList();
    return list;
  }

My bloc

class FlashCardSettingCubit extends Cubit<FlashCardSettingState> {
  FlashCardSettingCubit() : super(FlashCardSettingState.initial());

  void chooseFrontCardSetting(FrontSideDisplaySetting setting) {
    final isContain = state.frontSidesSetting.contains(setting);
    if (isContain) {
      state.frontSidesSetting.remove(setting);
    } else {
      state.frontSidesSetting.add(setting);
    }
    emit(state.copyWith(frontSidesSetting: List.from(state.frontSidesSetting)));
  }

  void chooseBackCardSetting(BackSideDisplaySetting setting) {
    final isContain = state.backSidesSetting.contains(setting);
    if (isContain) {
      state.backSidesSetting.remove(setting);
    } else {
      state.backSidesSetting.add(setting);
    }
    emit(state.copyWith(backSidesSetting: List.from(state.backSidesSetting)));
  }

}

My state class

enum FlashCardSettingStatus { selected, cancel, initial }

class FlashCardSettingState extends Equatable {
  final List<FrontSideDisplaySetting> frontSidesSetting;
  final List<BackSideDisplaySetting> backSidesSetting;
  final FlashCardSettingStatus status;

  @override
  List<Object> get props => [frontSidesSetting, backSidesSetting, status];

  const FlashCardSettingState({
    required this.frontSidesSetting,
    required this.backSidesSetting,
    required this.status,
  });

  FlashCardSettingState.initial()
      : this(
            frontSidesSetting: const [],
            backSidesSetting: const [],
            status: FlashCardSettingStatus.initial);

  FlashCardSettingState copyWith({
    List<FrontSideDisplaySetting>? frontSidesSetting,
    List<BackSideDisplaySetting>? backSidesSetting,
    FlashCardSettingStatus? status,
  }) {
    return FlashCardSettingState(
      frontSidesSetting: frontSidesSetting ?? this.frontSidesSetting,
      backSidesSetting: backSidesSetting ?? this.backSidesSetting,
      status: status ?? this.status,
    );
  }
}

Solution

  • The problem is following line

    emit(state.copyWith(frontSidesSetting: List.from(state.frontSidesSetting)));

    Here you are emitting same state so cubit does not produce this event. What you want is

        final frontSidesSetting = List.from(state.frontSidesSetting);
        final isContain = fromSideSetting.contains(setting);
        if (isContain) {
          frontSidesSetting.remove(setting);
        } else {
          frontSidesSetting.add(setting);
        }
        emit(state.copyWith(frontSidesSetting: frontSidesSetting));
    

    This way, the current state frontSidesSetting and value to be emitted will be different.