felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.56k stars 3.37k forks source link

BlocBuilder widget not rebuilding the ui on certain states. #4130

Closed Awakennn21 closed 3 months ago

Awakennn21 commented 3 months ago

Hi, So I'am having a problem with the BlocBuilder widget after certain state emmisions. The BlocBuilder widget nested inside the BlocListener that is also nested inside BlocProvider(To simplify things). The Issue that I have encountered is that the BlocBuilder only rebuilds then AuthenticationInitial is emitted and when the UserCreationSuccesfull is emitted. On the other hand The BlocListener reacts to every state change. As you can see in the code provided below on CreateUser event I'am emitting two states but i just can't get BlocBuilder to rebuild on CreatingUser state emmision. Here is my code: BlocProvider I am using GetIt package

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      lazy: false,
      create: (_) => serviceLocator<AuthenticationBloc>(),
      child: _ShouldDisplayLogin ? LoginPage(showRegisterPage: SwitchShownPages) : RegisterPage(showLoginPage: SwitchShownPages)
    );
  }

The UI

 @override
  Widget build(BuildContext context) 
  {
    return BlocListener<AuthenticationBloc, AuthenticationState>(
      listenWhen: (previous, current) => true,
      listener: (context, state) 
      {
        print('${state.runtimeType} state caught in the listener');
      },
      child: Scaffold(
          backgroundColor: ColorConstants.Color4,
          body: SafeArea(
            child: Center(
              child: SingleChildScrollView(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    //Some other irrelevant widgets

                    BlocBuilder<AuthenticationBloc, AuthenticationState>(
                      buildWhen: (previous, current) => true,
                      builder: (context, state) 
                      {
                        print('${state.runtimeType} state caught in the builder');

                        if(state is CreatingUser)
                        {
                          return const LoadingSubmitButtonWidget(
                            buttonColor: ColorConstants.Color1
                          );
                        }
                        else
                        {
                          return SubmitButtonWidget(
                            onTapAction: _RegisterNewUser,
                            buttonColor: ColorConstants.Color1,
                            text: 'Register'
                          );
                        }
                      },
                    ),
                     //Some other irrelevant widgets
                      ],
                    )
                  ],
                )
              )
            )
          )
        )
    );
  }

Bloc

class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> 
{
  AuthenticationBloc({required CreateNewUser createNewUser, required AuthenticateUser authenticateUser}) 
    : _CreateNewUser = createNewUser, _AuthenticateUser = authenticateUser, super(AuthenticationInitial()) 
  {
    on<CreateUserEvent>(_OnCreateUserHandler);
  }

  _OnCreateUserHandler(CreateUserEvent event, Emitter<AuthenticationState> emit) async
  {
    emit(const CreatingUser());

    final creationResult = await _CreateNewUser(event._UserDataParameters);
    creationResult.fold(
      (failure) => emit(UserCreationFailure(ErrorMessage: failure.ErrorMessage)), 
      (_) => emit(const UserCreationSuccesful())
    );
  }

  final CreateNewUser _CreateNewUser; //usecase 
}

States

sealed class AuthenticationState extends Equatable {
  const AuthenticationState();

  @override
  List<Object> get props => [];
}

final class AuthenticationInitial extends AuthenticationState {}

class CreatingUser extends AuthenticationState
{
  const CreatingUser();

  @override
  String toString() => 'CreatingUser';
}

class UserCreationSuccesful extends AuthenticationState
{
  const UserCreationSuccesful();

  @override
  String toString() => 'User has been created succesfully';
}

class UserCreationFailure extends AuthenticationState
{
  const UserCreationFailure({required this.ErrorMessage});

  @override
  List<Object> get props => [ErrorMessage];

  final String ErrorMessage;
}

Events

sealed class AuthenticationEvent extends Equatable 
{
  const AuthenticationEvent();

  @override
  List<Object> get props => [];
}

class CreateUserEvent extends AuthenticationEvent 
{
  const CreateUserEvent({required UserDataParameters userParams})
    : _UserDataParameters = userParams;

  final UserDataParameters _UserDataParameters;

  @override
  List<Object> get props => [_UserDataParameters];
}

Any hellp would be much appreciated

felangel commented 3 months ago

Hi @Awakennn21 ๐Ÿ‘‹ Thanks for opening an issue!

Can you please share a link to a minimal reproduction sample either on GitHub or DartPad? It would be much easier to help if we're able to run/reproduce the issue locally, thanks!

Awakennn21 commented 3 months ago

Hi, thank you so much for responding. Here is the link to the GitHub repo, containing the whole feature: https://github.com/Awakennn21/Temp

felangel commented 3 months ago

Hi @Awakennn21 ๐Ÿ‘‹ This is because you're registering your bloc as a factory which means each time you use get_it to lookup a bloc, you'll get a different instance and so the BlocBuilder is listening to changes in a different instance than the once you are adding events to. The quick fix would be to change your registration to registerSingleton instead of registerFactory but in general I recommend just providing blocs using BlocProvider and to avoid mixing it with get_it for this exact reason.

Hope that helps, closing for now!

Awakennn21 commented 3 months ago

Hi @felangel Thanks for your response. Just to be 100% sure. Do you want me to instead of using GetIt package inside the BlocProvider's create: function and swap that for manually creating the whole bloc there like this:

return BlocProvider(
      lazy: false,
      create: (_) => AuthenticationBloc(
        createNewUser: CreateNewUser(
          repository: AuthenticationRepository_impl(
            dataSource: const FireBaseRemoteDataSource()
          )
        ),
        authenticateUser: AuthenticateUser(
          repository: AuthenticationRepository_impl(
            dataSource: const FireBaseRemoteDataSource()
          )
        )
      ),
      child: _ShouldDisplayLogin ? LoginPage(showRegisterPage: SwitchShownPages) : RegisterPage(showLoginPage: SwitchShownPages)
    );

The problem is that this change did not help and the Bloc builder is still only reacting to the UserCreationSuccesfull, omitting the CreatingUserState. I have updated the given above repo and now there is no Get_It package in use yet the problem still remains. Any help would be appriciated as I am pretty stuck on this

felangel commented 2 months ago

@Awakennn21 it's possible for a bloc to emit states faster than the UI can rebuild. For example:

emit(StateA());
emit(StateB());

In the above scenario the bloc will emit StateA and StateB before the UI can rebuild so you shouldn't assume in your BlocBuilder that builder will run with each state. This is not specific to bloc, the same would happen if you called setState back-to-back.

Hope that helps ๐Ÿ‘

Awakennn21 commented 2 months ago

Thank you very much. Now everything is working perfectly ๐Ÿ˜Š