FLUTTER -BLOC is not updating state even with copyWith() and equatable

2.1k Views Asked by At

I have created an event for radio button like functionality where user can select only single element at a time. But when I trigger the event to select the value, it does not update the state, however the list does get updated.

This is my bloc file

class SlotsTabbarBloc extends Bloc<SlotsTabbarEvent, SlotsTabbarState> {

 SlotsTabbarSuccessState slotsTabbarSuccess =
      SlotsTabbarSuccessState(slotData: const [], enableButton: true);

 SlotsTabbarBloc(
    this.slotsUsecase,
  ) : super(SlotsTabbarInitial()) {
    SlotsTabbarLoading();

 on<SelectAvailablePanelRadioButtonEvent>(
      _selectAvailablePanelRadioButtonEvent,
    );
  }
Future<void> _selectAvailablePanelRadioButtonEvent(
    SelectAvailablePanelRadioButtonEvent event,
    Emitter<SlotsTabbarState> emit,
  ) async {
    bool isAvailableSlotRadioEnabled = false;

    for (int i = 0; i < event.dummyData.length; i++) {
      event.dummyData[i].isSelected =
          i == event.index ? isAvailableSlotRadioEnabled = true : false;
    }

    slotsTabbarSuccess = slotsTabbarSuccess.copyWith(
      isAvailableSlotRadioEnabled: isAvailableSlotRadioEnabled,
      availableSlotEntity: event.availableSlotEntity,
      dummyData: event.dummyData,
    );
    emit(slotsTabbarSuccess);
  }
}

This is the event class


class SelectAvailablePanelRadioButtonEvent extends SlotsTabbarEvent {
  bool isAvailableSlotRadioEnabled;
  final int index;
  final AvailableSlotEntity availableSlotEntity;
  final List<DummyData> dummyData;
    final List<InterviewSlotDtoListEntity> slotData;
  SelectAvailablePanelRadioButtonEvent({
    required this.isAvailableSlotRadioEnabled,
    required this.availableSlotEntity,
    required this.index,
    required this.dummyData,
    required this.slotData,
  });
}

this is the state class


class SlotsTabbarSuccessState extends SlotsTabbarState {
  final List<InterviewSlotDtoListEntity> slotData;
  final AvailableSlotEntity? availableSlotEntity;
  final SpecificBookedCancelledSlotDetailsEntity?
      specificBookedCancelledSlotDetailsEntity;

  final String reasonForCancellationEnum;
  final String cancellationReason;
  final List<Map<String, dynamic>>? radioMap;
  final bool enableButton;
  final bool isRadioButtonSelected;
  final bool isAvailableSlotRadioEnabled;
  bool isBookedResponseTrue;
  bool isCancelledResponseTrue;
  bool markSlotAvailable;

  final List<DummyData>? dummyData;

  SlotsTabbarSuccessState({
    this.dummyData,
    required this.slotData,
    this.specificBookedCancelledSlotDetailsEntity,
    this.reasonForCancellationEnum = 'CANDIDATE_CANCEL',
    this.availableSlotEntity,
    this.radioMap,

    // this.enableButton = true,
    this.enableButton = true,
    this.isAvailableSlotRadioEnabled = true,
    this.isRadioButtonSelected = false,
    this.isBookedResponseTrue = false,
    this.isCancelledResponseTrue = false,
    this.markSlotAvailable = false,
    this.cancellationReason = '',
  });

  SlotsTabbarSuccessState copyWith({
    final List<DummyData>? dummyData,
    String? cancellationReason,
    List<InterviewSlotDtoListEntity>? slotData,
    AvailableSlotEntity? availableSlotEntity,
    SpecificBookedCancelledSlotDetailsEntity?
        specificBookedCancelledSlotDetailsEntity,
    String? reasonForCancellationEnum = 'CANDIDATE_CANCEL',
    List<Map<String, dynamic>>? radioMap,
    bool? isBookedResponseTrue = false,
    bool? isCancelledResponseTrue = false,
    bool? markSlotAvailable = false,
    bool? isRadioButtonSelected = false,
    bool? isAvailableSlotRadioEnabled,
    //bool? enableButton ,
    bool? enableButton,
  }) {
    return SlotsTabbarSuccessState(
      dummyData: dummyData ?? this.dummyData,
      cancellationReason: cancellationReason ?? this.cancellationReason,
      radioMap: radioMap ?? this.radioMap,
      isAvailableSlotRadioEnabled:
          isAvailableSlotRadioEnabled ?? this.isAvailableSlotRadioEnabled,
      enableButton: enableButton ?? this.enableButton,
      isRadioButtonSelected:
          isRadioButtonSelected ?? this.isRadioButtonSelected,
      markSlotAvailable: markSlotAvailable ?? this.markSlotAvailable,
      reasonForCancellationEnum:
          reasonForCancellationEnum ?? this.reasonForCancellationEnum,
      slotData: slotData ?? this.slotData,
      specificBookedCancelledSlotDetailsEntity:
          specificBookedCancelledSlotDetailsEntity ??
              this.specificBookedCancelledSlotDetailsEntity,
      availableSlotEntity: availableSlotEntity ?? this.availableSlotEntity,
      isBookedResponseTrue: isBookedResponseTrue ?? this.isBookedResponseTrue,
      isCancelledResponseTrue:
          isCancelledResponseTrue ?? this.isCancelledResponseTrue,
    );
  }

  @override
  List<Object> get props => [
        slotData,
        reasonForCancellationEnum,
        isBookedResponseTrue,
        isCancelledResponseTrue,
        markSlotAvailable,
        enableButton,
        isAvailableSlotRadioEnabled,
        isRadioButtonSelected,
        cancellationReason,
      ];
}

here is how i am calling the bloc in the UI. Whenever I tap on the radio button, it should trigger the event, and the event should emit the state. The event gets triggered but the UI does not update on the screen. It updates after I refresh the screen.

import 'package:auto_route/auto_route.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class DummyData {
  final String name;
  final String designation;
  bool isSelected = false;
  DummyData(
      {required this.name,
      required this.designation,
      required this.isSelected});
}

class AvailableOnClickWidget extends StatelessWidget {
  final String slotStartTime;
  final String slotEndTime;
  final bool showCheckBox;
  final List<InterviewSlotDtoListEntity> slotData;
  final String levelId;
  final String techStackId;
  bool isRadioButtonEnabled;

  AvailableOnClickWidget({
    super.key,
    required this.slotData,
    required this.slotStartTime,
    required this.slotEndTime,
    required this.showCheckBox,
    required this.isRadioButtonEnabled,
    required this.techStackId,
    required this.levelId,
  });
  TextEditingController candidateNameContoller = TextEditingController();
  // List<DummyData> dummyData = [
  //   DummyData(name: 'name', designation: 'designation', isSelected: false),
  //   DummyData(name: 'name', designation: 'designation', isSelected: false),
  //   DummyData(name: 'name', designation: 'designation', isSelected: false),
  //   DummyData(name: 'name', designation: 'designation', isSelected: false),
  // ];

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => Injector.resolve<SlotsTabbarBloc>(),
      child: BlocBuilder<SlotsTabbarBloc, SlotsTabbarState>(
        builder: (context, state) {
          if (state is SlotsTabbarInitial) {
            BlocProvider.of<SlotsTabbarBloc>(context).add(
              GetSpecificAvailableSlotDetailsEvent(
                slotData: slotData,
                fromTime: slotData[0].interviewSlotTimeDTOList[0].fromTime!,
                toTime: slotData[0].interviewSlotTimeDTOList[0].toTime!,
                slotDate: slotData[0].date!,
                interviewLevelId: levelId,
                techStackId: techStackId,
              ),
            );
            return const Center(
              child: CircularProgressIndicator(),
            );
          } else if (state is SlotsTabbarSuccessState) {
            if (state.isBookedResponseTrue) {
              CustomBotToastWidget.showToastWidget(
                context: context,
                onProgressBarCompletion: () {},
                toastMessage: StringConstants.slotMarkedAsBooked,
                showLinearProgressIndicator: false,
                showToastActionButton: false,
                cancelOnTap: () {
                  BotToast.cleanAll();
                },
              );
            }
            return SizedBox(
              height: MediaQuery.of(context).size.height * 0.95,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  24.verticalSpace,
                  TitleWithCancelButtonWidget(
                    title:
                        '${state.availableSlotEntity!.slotDate}, $slotStartTime - $slotEndTime',
                    textStyle: TextStyleConstants.s14w600c101828fMontserrat,
                  ),
                  40.verticalSpace,
                  Flexible(
                    fit: FlexFit.loose,
                    child: ListView.separated(
                      shrinkWrap: true,
                      padding: EdgeInsets.zero,
                      itemBuilder: (context, index) {
                        return AvailableOnClickPopUpListTile(
                            radioOnTap: () {
                              BlocProvider.of<SlotsTabbarBloc>(context).add(
                                SelectAvailablePanelRadioButtonEvent(
                                  slotData: state.slotData,
                                  availableSlotEntity:
                                      state.availableSlotEntity!,
                                  isAvailableSlotRadioEnabled:
                                      state.dummyData![index].isSelected,
                                  index: index,
                                  dummyData: state.dummyData!,
                                ),
                              );
                            },
                            imageUrl:
                                "https://lh3.googleusercontent.com/a/AEdFTp439zX_GEWqClFIwDVMbf8eUuCvqcLvjdiQcslZ=s96-c",
                            title: state.dummyData![index].name,
                            subtitle: state.dummyData![index].designation,
                            chipContainerText: state.dummyData![index].name,
                            isRadioButtonEnabled:
                                state.dummyData![index].isSelected
                            // ==
                            //     state.isAvailableSlotRadioEnabled,
                            //,
                            );
                        // return AvailableOnClickPopUpListTile(
                        //   imageUrl: state.availableSlotEntity!
                        //       .slotUserDetailList[0].imageUrl,
                        //   title:
                        //       '${state.availableSlotEntity!.slotUserDetailList[0].firstName} ${state.availableSlotEntity!.slotUserDetailList[0].lastName}',
                        //   subtitle: state.availableSlotEntity!
                        //       .slotUserDetailList[0].designation,
                        //   chipContainerText: state.availableSlotEntity!
                        //       .slotUserDetailList[0].levelOfInterview,
                        //   isRadioButtonEnabled:
                        //       state.isAvailableSlotRadioEnabled,
                        //   radioOnTap: () {
                        //     BlocProvider.of<SlotsTabbarBloc>(context).add(
                        //       SelectAvailablePanelRadioButtonEvent(
                        //         availableSlotEntity: state.availableSlotEntity!,
                        //         isAvailableSlotRadioEnabled: state
                        //             .availableSlotEntity!
                        //             .slotUserDetailList[0]
                        //             .isSelected,
                        //       ),
                        //     );
                        //   },
                        // );
                      },
                      separatorBuilder: (context, index) {
                        return 32.verticalSpace;
                      },
                      itemCount: state.dummyData!.length,
                      //    state.availableSlotEntity!.slotUserDetailList.length,
                    ),
                  ),
                  40.verticalSpace,
                  ColumnWithHeadingTextFieldAndIsmFilledButton(
                    reasonForCancellationController: candidateNameContoller,
                    isAvailableOnClickWidget: true,
                    isCancelledOnClickWidget: false,
                    onPressed: () {
                      BlocProvider.of<SlotsTabbarBloc>(context).add(
                        MarkAsBookedEvent(
                          candidateName: candidateNameContoller.text,
                          interviewLevelId: levelId,
                          techStackId: techStackId,
                          slotId: state.availableSlotEntity!
                              .slotUserDetailList[0].slotId,
                        ),
                      );

                      context.router.pop();
                    },
                    heading:
                        '${StringConstants.candidateName}${StringConstants.asterisk}',
                    maxLines: 1,
                    checkBoxOnChanged: (value) {},
                    textFieldOnChange: (value) {
                      BlocProvider.of<SlotsTabbarBloc>(context).add(
                        CandidateNameChangedEvent(
                          candidateName: candidateNameContoller.text,
                          enableButton: state.enableButton,
                        ),
                      );
                    },
                    checkBoxBooleanValue: true,
                    ismFilledButtonText: StringConstants.markAsBooked,
                    isIsmButtonValid: state.enableButton,
                    showCheckBox: showCheckBox,
                  ),
                  24.verticalSpace,
                ],
              ),
            );
          }
          return const SizedBox.shrink();
        },
      ),
    ).symmetricHorizontalPadding(16);
  }
}

4

There are 4 best solutions below

1
On

I don't know if this will work but try this : in SlotsTabbarBloc bloc _selectAvailablePanelRadioButtonEvent function at the end change this :

slotsTabbarSuccess = slotsTabbarSuccess.copyWith(
    isAvailableSlotRadioEnabled: isAvailableSlotRadioEnabled,
    availableSlotEntity: event.availableSlotEntity,
    dummyData: event.dummyData,
);
emit(slotsTabbarSuccess);

to this :

emit(slotsTabbarSuccess.copyWith(
    isAvailableSlotRadioEnabled: isAvailableSlotRadioEnabled,
    availableSlotEntity: event.availableSlotEntity,
    dummyData: event.dummyData,
));

also remove the BlocProvider form the AvailableOnClickWidget and add it the main.dart before the MaterialApp something like this :

MultiBlocProvider(
    providers: [
        BlocProvider<SlotsTabbarBloc>(
            create: (context) => SlotsTabbarBloc(),
        ),
     ],
     child: const MaterialApp(
         ...
     ),
 ),
0
On

Add emit and work perfectally.

on((result, emit) => emit(InternetGainState())); on((result, emit) => emit( InternetLostState()));

0
On

In my case I did not override the value of equatable.So, I faced the problem. But then I solve it like this :

class UserImageLoaded extends UserImageState {
  final List<UserImageModel> userImage;

  UserImageLoaded(this.userImage) {
   print("UserImageLoaded called");
    }
  @override
  List<Object> get props => [userImage];
   }

here I did not give the userImage value in my userImageLoaded state. Then I solve it by this code.

0
On

If you use the Equatable package and update the -State object, or values/objects that it contains, but you haven't added every single field to the props array getter, even if far down the object hierarchy, and you update one of these fields that is NOT in the props array, then the bloc infrastructure won't update the bloc's state (but when you unit-test the state it works, because there are no optimisations applied). So go back and ensure that you have every single prop in the props array.