Search code examples
flutterdartblocflutter-bloc

Flutter Application Bloc listener not receiving state updates


I'm using flutter Bloc to navigate the user toward either the login page or home screen depending on whether they are authenticated or not. However, after the initial state change, the listener doesn't trigger when I change my authentication state.

Listener:

Widget build(BuildContext context) {
  return BlocListener<AuthenticationBloc, AuthenticationState>(
    listener: (context, state) {
      // Listener never gets called on statechange after the initial app startup

      // navigation based on authentication 
      }
    },
    child: SplashPage(),
  );
}

The provider gets initialized in the parent widget:

AuthenticationRepository authRepo = AuthenticationRepository();

Widget build(BuildContext context) {
  return MultiBlocProvider(
    providers: [
      BlocProvider<AuthenticationBloc>(
        create: (BuildContext context) =>
            AuthenticationBloc(authenticationRepository: authRepo),
      ),
      /*
         Other Providers
      */
    ],
    child: MaterialApp(
      title: 'myApp',
      home: StartUpPage(),
    ),
  );

When the user logs in mapEventState gets called in the AuthenticationBloc:

class AuthenticationBloc
    extends Bloc<AuthenticationEvent, AuthenticationState> {
  AuthenticationBloc({
    @required AuthenticationRepository authenticationRepository,
  })  : assert(authenticationRepository != null),
        _authenticationRepository = authenticationRepository,
        super(const AuthenticationState.unknown()) {
    _userSubscription = _authenticationRepository.userStream.listen(
      (user) => add(AuthenticationUserChanged(user)),
    );
  }

  final AuthenticationRepository _authenticationRepository;
  StreamSubscription<User> _userSubscription;

  @override
  Stream<AuthenticationState> mapEventToState(
    AuthenticationEvent event,
  ) async* {
    if (event is AuthenticationUserChanged) {
      yield _mapAuthenticationUserChangedToState(event);
    } else if (event is AuthenticationLogoutRequested) {
      unawaited(_authenticationRepository.logOut());
    }
  }

  @override
  Future<void> close() {
    _userSubscription?.cancel();
    return super.close();
  }

  AuthenticationState _mapAuthenticationUserChangedToState(
    AuthenticationUserChanged event,
  ) =>
      event.user != User.empty
          ? AuthenticationState.authenticated(event.user)
          : const AuthenticationState.unauthenticated();
}

I'd expect the listener to trigger when the user logs in and the AuthenticationState changes. If anyone knows what I'm doing wrong or if I'm missing something I'd love to hear it.

EDITED 08-02-2021:

I've checked again if the state changes after login in using a simple button. With this, I can confirm that the state does change and holds the correct user data and authentication status. Another thing I confirmed is that a BlocBuilder that is using a BlocBuilder<AuthenticationBloc, AuthenticationState> IS updating correctly when a user logs in.

EDITED 10-02-2021:

Entire Authentication state:

enum AuthenticationStatus { authenticated, unauthenticated, unknown }

class AuthenticationState extends Equatable {
  const AuthenticationState._({
    this.status = AuthenticationStatus.unknown,
    this.user = User.empty,
  });

  const AuthenticationState.unknown() : this._();

  const AuthenticationState.authenticated(User user)
      : this._(status: AuthenticationStatus.authenticated, user: user);

  const AuthenticationState.unauthenticated()
      : this._(status: AuthenticationStatus.unauthenticated, user: User.empty);

  final AuthenticationStatus status;
  final User user;

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

EDITED 12-02-2021:

removed non-relevant code

EDITED 15-02-2021:

Added entire Authentication BloC


Solution

  • Can you list your entire widget tree?

    I'm wondering if your first widget (the one containing the Listener) isn't actually being rendered (say, because you have an if/else somewhere in the tree that means it's not actually being shown at the time the event is fired).

    You can check this with the Flutter inspector. Otherwise, is it running in the same BuildContext?

    If you showed the whole widget tree, this would be easier to diagnose.