bottom sheet is showing repeatedly once event triggred using bloc

Issue

I am a newbie, learning how to use bloc with freezed. i created a bottom sheet when the user click to the float action button, the bottom sheet appears. Bottom sheet contains text field and three radio groups when i click to select a radio the bottom sheet popup again like this GIF.
https://drive.google.com/file/d/1iU06adGcwjEaw9z2LsmO5xS24CC6OQfT/view?usp=sharing

the bloc is:

class NoteBloc extends Bloc<NoteEvent, NoteState> {
  NoteBloc() : super(NoteState.initial());

  @override
  Stream<NoteState> mapEventToState(
    NoteEvent event,
  ) async* {
    yield* event.map(
      addNewNoteButtonEvent: (AddNewNoteButtonEvent e) async* {
          yield state.copyWith(
            isAddNewNoteButtonClickedState: e.isClickedEvent,
          );
      },
      radioEvent: (RadioEvent e) async* {
        yield state.copyWith(radioGroupState: e.value);
      },
      textInputEvent: (TextInputEvent e) async* {
        yield state.copyWith(textInputState: Note(value: e.value));
      },
    );
  }
}

the event is:

class NoteEvent with _$NoteEvent {
  const factory NoteEvent.addNewNoteButtonEvent(
      {required bool isClickedEvent}) = AddNewNoteButtonEvent;
  const factory NoteEvent.radioEvent({required int value}) = RadioEvent;
  const factory NoteEvent.textInputEvent({required String value}) =
      TextInputEvent;
}

the state is:

class NoteState with _$NoteState {
  const factory NoteState({
    // required bool showSaveIconState,
    required int radioGroupState,
    required bool isAddNewNoteButtonClickedState,
    required Note textInputState,
  }) = _NoteState;
  // initialize every state
  factory NoteState.initial() => NoteState(
        radioGroupState: 1,
        isAddNewNoteButtonClickedState: false,
        textInputState: Note(value: ''),
      );
}

the home page code is:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
    return BlocConsumer<NoteBloc, NoteState>(
      listener: (context, state) {
        // if (state is AddNoteClickedState) {
        if (state.isAddNewNoteButtonClickedState) {
          
          // show navigator
          _scaffoldKey.currentState!
              .showBottomSheet((context) => const AddNewNoteBottomSheet())
              .closed
              .then(
            (value) {
              // Change state
              // to tell the bloc that
              // the bottom sheet is closed
              context.read<NoteBloc>().add(
                    const NoteEvent.addNewNoteButtonEvent(
                      isClickedEvent: false, // !state.isAddNewNoteClickedState,
                    ),
                  );
            },
          );
        }
      },
      builder: (context, state) {
        return DefaultTabController(
          length: 3,
          child: Scaffold(
            key: _scaffoldKey,
            // add note float action button
            floatingActionButton: InkWell(
              onTap: () {
                // make button clicked
                // to open the bottom sheet
                context.read<NoteBloc>().add(
                      const NoteEvent.addNewNoteButtonEvent(
                        isClickedEvent: true, //!state.isAddNewNoteClickedState,
                      ),
                    );
                // print(state);
              },
              // for float action button
              // icon shape
              child: Container(
                clipBehavior: Clip.antiAliasWithSaveLayer,
                height: 11.h,
                width: 10.h,
                decoration: BoxDecoration(
                  shape: BoxShape.rectangle,
                  borderRadius: BorderRadius.all(Radius.circular(1.w)),
                  color: const Color(accentColor),
                ),
                // add note icon
                child: Icon(
                  state.isAddNewNoteButtonClickedState ? Icons.save : Icons.add,
                  color: const Color(whiteColor),
                  size: 9.h,
                ),
              ),
        );
      },
    );
  }
}

AddNewNoteBottomSheet code is:

// For adding new note
class AddNewNoteBottomSheet extends StatelessWidget {
  const AddNewNoteBottomSheet();

  @override
  Widget build(BuildContext context) {
    TextEditingController _textEditingController = TextEditingController();
    return Container(
      // Contains all
      // bottom sheet components
      child: Column(
              children: [
          // bottom sheet body
          Expanded(
            child: SingleChildScrollView(
              child: BlocBuilder<NoteBloc, NoteState>(
                builder: (context, state) {
                  // print("Helllo ${state.maybeMap(orElse: (){},radioClickState: (stat)=>stat.value)}");
                  return Column(
                    children: [
                      
                      // Today Radio
                      ListTile(
                        title: Text(
                          AppLocalizations.of(context)!.homeTodayTapTitle,
                          style: Theme.of(context)
                              .tabBarTheme
                              .unselectedLabelStyle,
                        ),
                        contentPadding: const EdgeInsets.all(0),
                        autofocus: true,
                        leading: Radio<int>(
                          value: 1,
                          //get group value
                          groupValue: state.radioGroupState,
                          onChanged: (value) {
                            // tell bloc i click you
                            context
                                .read<NoteBloc>()
                                .add(NoteEvent.radioEvent(value: value!));
                          },
                        ),
                      ),
                      // Tomorrow Radio
                      ListTile(
                        contentPadding: const EdgeInsets.all(0),
                        title: Text(
                          AppLocalizations.of(context)!.homeTomorrowTapTitle,
                          style: Theme.of(context)
                              .tabBarTheme
                              .unselectedLabelStyle,
                        ),
                        leading: Radio<int>(
                          value: 2,
                          //get group value
                          groupValue: state.radioGroupState,
                          onChanged: (value) {
                            // tell bloc i click you
                            context.read<NoteBloc>().add(
                                  NoteEvent.radioEvent(value: value!),
                                );
                          },
                        ),
                      ),
                      // Some Day Radio
                      ListTile(
                        contentPadding: const EdgeInsets.all(0),
                        title: Text(
                          AppLocalizations.of(context)!.homeSomeDayTapTitle,
                          style: Theme.of(context)
                              .tabBarTheme
                              .unselectedLabelStyle,
                        ),
                        leading: Radio<int>(
                          value: 3,
                          //get group value
                          groupValue: state.radioGroupState,

                          onChanged: (value) {
                            // tell bloc i click you
                            context.read<NoteBloc>().add(
                                  NoteEvent.radioEvent(value: value!),
                                );
                          },
                        ),
                      ),
                    ],
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Solution

Problem definition: This problem happens because you use state.copyWith on your bloc. When you use copyWith, even if you don’t update isAddNewNoteButtonClickedState variable, your state persists that value (stays true after you set it, never changes if you don’t change it) if you do not alter it. Because on copyWith method, update logic works like;

YourState copyWith(bool isAddNewNoteButtonClickedState) {
   return YourState(isAddNewNoteButtonClickedState: isAddNewNoteButtonClickedState ?? this. isAddNewNoteButtonClickedState,)
}

And when you yield another state, this isAddNewNoteButtonClickedState stays true, and your listener shows another modal since your isAddNewNoteButtonClickedState did not change.

Solution: you can solve this problem by (adding isAddNewNoteButtonClickedState: false to other states):

    class NoteBloc extends Bloc<NoteEvent, NoteState> {
  NoteBloc() : super(NoteState.initial());

  @override
  Stream<NoteState> mapEventToState(
    NoteEvent event,
  ) async* {
    yield* event.map(
      addNewNoteButtonEvent: (AddNewNoteButtonEvent e) async* {
          yield state.copyWith(
            isAddNewNoteButtonClickedState: e.isClickedEvent,
          );
      },
      radioEvent: (RadioEvent e) async* {
        yield state.copyWith(radioGroupState: e.value, isAddNewNoteButtonClickedState: false);
      },
      textInputEvent: (TextInputEvent e) async* {
        yield state.copyWith(textInputState: Note(value: e.value), isAddNewNoteButtonClickedState: false);
      },
    );
  }
}

Additional note:
Since you are not transforming events in bloc, or doing something trivial, you can use cubit to simplify your state management and business logic.

Answered By – Emir Bostanc─▒

Answer Checked By – Mildred Charles (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.