Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Bloc Consumer's listener is passing the previous instance of the bloc to the method #4162

Closed
yarenalbayr opened this issue May 3, 2024 · 2 comments
Labels
question Further information is requested

Comments

@yarenalbayr
Copy link

Issue: State Reversion after Navigation in Flutter Bloc

Description

I have a Flutter app where the UserEditProfileBloc is used in the "Edit Profile Details" screen. The bloc is created using a FutureBuilder that fetches the user's profile details. When navigating to a separate screen using BlocConsumer's listener and passing the bloc instance through the Navigator to edit certain details, the state is updated correctly. However, after popping back to the "Edit Profile Details" screen and navigating again, the previous state is passed.

Reproduction Steps

  1. Initial Setup:

    • The UserEditProfileBloc is initialized using BlocProvider within a FutureBuilder.
    • This Future fetches user profile details, passing the data into the bloc's constructor.
  2. Navigating to the Editing Screen:

    • The user navigates to a specific editing screen by triggering a navigation event.
    • The BlocConsumer's listener is used to handle navigation events.
  3. Editing and Returning:

    • After performing edits and popping back to the "Edit Profile Details" screen, the updated data is correctly reflected.
  4. Navigating Again:

    • When navigating to the same editing screen again, the previous state (from before the first edit) is shown instead of the updated state.

Bloc Setup

The UserEditProfileBloc handles two types of events:

  • Profile Update Events: These events handle updating profile details, such as interests, in the database.
  • Navigation Events: These events handle navigation actions.

Code

The issue might relate to how the bloc state is handled across navigation. Below are snippets of the bloc, the screen setup, and navigation:

1. Bloc Setup

part 'user_edit_profile_state.dart';

class UserEditProfileBloc
    extends Bloc<UserEditProfileEventBase, UserEditProfileState> {
  final UsersRepository userRepository;
  final UserProfileDetailsDM userProfileDetails;

  UserEditProfileBloc(
      {required this.userRepository, required this.userProfileDetails})
      : super(UserEditProfileInitial(userProfileDetails)) {
    on<UserEditProfileEvent>(_onUserEditProfileEvent);
    on<NavigationActionEvent>(_onUserEditProfileNavigationEvent);
  }

  Future<void> _onUserEditProfileEvent(
      UserEditProfileEvent event, Emitter<UserEditProfileState> emit) async {
    emit(UserEditProfileLoading(this.userProfileDetails));
    try {
      final result = await event.map(
        updateInterests: (e) => userRepository.updateInterests(e.interests),
        //other methods to call repository
      );
      result.fold(
        (userProfileDetails) =>
            emit(UserEditProfileSuccess(userProfileDetails)),
        (exception) => emit(UserEditProfileFailureActionState(
            this.userProfileDetails, exception.toString())),
      );
    } catch (exception) {
      emit(UserEditProfileFailureActionState(
          this.userProfileDetails, exception.toString()));
    }
  }

  FutureOr<void> _onUserEditProfileNavigationEvent(
      NavigationActionEvent event, Emitter<UserEditProfileState> emit) {
    emit(UserEditProfileLoading(this.userProfileDetails));
    emit(UserEditProfileNavigationActionState(
      this.userProfileDetails,
      event.destination,
    ));
  }
}

Using freezed package for bloc's events

part 'user_edit_profile_event.freezed.dart';

abstract class UserEditProfileEventBase {}

abstract class UserEditProfileNavigationEventBase
    extends UserEditProfileEventBase {}

@freezed
sealed class UserEditProfileEvent extends UserEditProfileEventBase
    with _$UserEditProfileEvent {
  const factory UserEditProfileEvent.updateInterests(
      List<String> interests) = UpdateInterests;
  // Other events
}

@freezed
sealed class UserEditProfileNavigationEvent
    extends UserEditProfileNavigationEventBase
    with _$UserEditProfileNavigationEvent {
  const factory UserEditProfileNavigationEvent.navigationActionEvent(
    UserEditProfileActionType destination, // this is just an enum for the navigation action types
  ) = NavigationActionEvent;

}

My bloc


part 'user_edit_profile_state.dart';

class UserEditProfileBloc
    extends Bloc<UserEditProfileEventBase, UserEditProfileState> {
  final UsersRepository userRepository;
  final UserProfileDetailsDM userProfileDetails;

  UserEditProfileBloc(
      {required this.userRepository, required this.userProfileDetails})
      : super(UserEditProfileInitial(userProfileDetails)) {
    on<UserEditProfileEvent>(_onUserEditProfileEvent);
    on<NavigationActionEvent>(_onUserEditProfileNavigationEvent);
  }

  Future<void> _onUserEditProfileEvent(
      UserEditProfileEvent event, Emitter<UserEditProfileState> emit) async {
    emit(UserEditProfileLoading(this.userProfileDetails));
    try {
      final result = await event.map(
        updateInterests: (e) => userRepository.updateInterests(e.interests),
        //other methods to call repository
      );
      result.fold(
        (userProfileDetails) =>
            emit(UserEditProfileSuccess(userProfileDetails)),
        (exception) => emit(UserEditProfileFailureActionState(
            this.userProfileDetails, exception.toString())),
      );
    } catch (exception) {
      emit(UserEditProfileFailureActionState(
          this.userProfileDetails, exception.toString()));
    }
  }

  FutureOr<void> _onUserEditProfileNavigationEvent(
      NavigationActionEvent event, Emitter<UserEditProfileState> emit) {
    emit(UserEditProfileLoading(this.userProfileDetails));
    emit(UserEditProfileNavigationActionState(
      this.userProfileDetails,
      event.destination,
    ));

  }

Edit Profile Details View

FutureBuilder<Result<UserProfileDetailsDM, Exception>>(
            future: container.resolve<UsersRepository>().getProfileDetails(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Center(child: CircularProgressIndicator());
              }
              if (snapshot.hasError) {
                return Center(
                    child: Text(l10n.editProfileDetailsErrorFetchingProfile));
              } else {
                return BlocProvider<UserEditProfileBloc>(
                    create: (context) => UserEditProfileBloc(
                        userRepository: container.resolve<UsersRepository>(),
                        userProfileDetails: snapshot.data!.getOrThrow()),
                    child:
                        BlocConsumer<UserEditProfileBloc, UserEditProfileState>(
                      listener: (context, state) {
                        if (state is UserEditProfileFailureActionState) {
                          ScaffoldMessenger.of(context).showSnackBar(
                            SnackBar(
                              content: Text(
                                  l10n.editProfileDetailsSomethingWentWrong),
                              backgroundColor: Colors.red,
                            ),
                          );
                        }
                        //Handling navigation via listener in here
                        if (state is UserEditProfileNavigationActionState) {
                          handleNavigationAction(
                              context,
                              state.destination,
                              context.read<UserEditProfileBloc>(),
                              state.profile);
                        }
                      },
                      buildWhen: (previous, current) =>
                          current is UserEditProfileSuccess,
                      listenWhen: (previous, current) =>
                          current is UserEditProfileActionState,
                      builder: (context, state) {
                        final bloc = context.watch<UserEditProfileBloc>();
                        final userProfileDetails = state.profile;
                        return Stack(
                          children: [
                            SingleChildScrollView(
                              child: Container(
                                margin: const EdgeInsets.all(16),
                                child: Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                   MyInterests(
                                     selectedInterests:
                                        userProfileDetails.interests?.toList(),
                                      userEditProfileBloc: bloc,
                                    ),
                              

My interest is showing a chip of widget in edit profile and it will call the navigation event on tap

In MyInterest widget there is 
 onTap: () {
                            widget.userEditProfileBloc.add(
                                UserEditProfileNavigationEvent
                                    .navigationActionEvent(
                              UserEditProfileActionType.interests,
                            ));
                          }),

The event that is in my


mixin EditProfileDetailsScreenMixin on State<EditProfileDetailsScreen

Future<void> handleNavigationAction(
      BuildContext context,
      UserEditProfileActionType actionType,
      UserEditProfileBloc bloc) async {
    switch (actionType) {
      case UserEditProfileActionType.interests:
        pushToInterests(context, bloc);
      case UserEditProfileActionType.location:
        //some other implementations
        
        
        
         Future<dynamic> pushToInterests(
      BuildContext context, UserEditProfileBloc bloc) {
    return Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => BlocProvider.value(
            value: bloc,
            child: SelectInterests(
              userEditProfileBloc: bloc,
            ),
          ),
        ));
}

The SelectInterests page shows the selected interests of that user and allows user to select more or less and on pop it calls

 void saveAndSubmit() {
  widget.userEditProfileBloc
      .add(UserEditProfileEvent.updateInterests(userInterests.value));
  Navigator.of(context).pop();
}
  ```
@yarenalbayr yarenalbayr added the bug Something isn't working label May 3, 2024
@felangel
Copy link
Owner

felangel commented May 4, 2024

Hi @yarenalbayr 👋
Thanks for opening an issue!

Are you able to provide a link to a minimal reproduction sample? It'd be much easier to help if I'm able to run your sample and reproduce/debug the issue locally.

@felangel felangel added waiting for response Waiting for follow up needs repro info The issue is missing a reproduction sample and/or steps labels May 4, 2024
@felangel
Copy link
Owner

Closing for now since there's no link to a minimal reproduction sample. If this is still an issue please share a link to a minimal reproduction sample and I'm more than happy to take a closer look, thanks!

@felangel felangel added question Further information is requested and removed bug Something isn't working waiting for response Waiting for follow up needs repro info The issue is missing a reproduction sample and/or steps labels May 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants