Flutter Application Bloc listener not receiving state updates
Asked Answered
T

3

9

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

Theolatheologian answered 5/2, 2021 at 18:4 Comment(10)
What does happen when you attempt change the state? Are you sure the state is actually changing?Gluttonize
@Gluttonize When a User gets pushed on the User stream following a sign in, an AuthenticationUserChanged event gets added to the authentication Bloc > mapEventToState > _mapAuthenticationUserChangedToState(event) > AuthenticationState.(un)authenticated gets called > a new Authentication state gets created and then yielded from mapEventToState. I've checked that these states are different.Theolatheologian
Can you provide the AuthenticationState code?Athenaathenaeum
@NabeelParkar Code added. On startup the state changes from unknown to unauthenticated with an empty user. This gets caught by the listener, subsequent changes do not.Theolatheologian
Can you provider "AuthenticationBloc" code completely? Also tell me where do you dispatch Authentication events?Apophyllite
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.Lea
@AlirezaAbiri Code added, an event gets added when a new User comes down the User stream: _authenticationRepository.userStream.listen( (user) => add(AuthenticationUserChanged(user))Theolatheologian
@NickFisher That was indeed the problem. Thank you so much. If you add it as an answer I can accept it as the solution. The navigation removed the page with the listener from the tree with pushAndRemoveUntilTheolatheologian
A bit off topic, but can you please tell what is the purpose of Authentication state unknown, i saw it in flutter_bloc docs also. I couldn't understand what is its purpose.Tactic
@sankethB.K As I'm no longer working on this project (internship), I'm not 100% certain but I believe it was used when you first startup the app and you're data has not yet been received from the database (in our case firebase). While it's still unknown you can eg. display a loading screenTheolatheologian
L
3

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.

Lea answered 15/2, 2021 at 11:27 Comment(0)
W
3

Without seeing the BLoC it's hard to tell but is it possible that you are creating the listener after the state been emitted. Bare in mind that Listener will not fire the last state, only the states been emitted after the creation of the listener.

Please share the BLoC code.

Whittle answered 15/2, 2021 at 8:11 Comment(2)
Added. As I mentioned before, the first time the state changes from unknown to unauthenticated, it does catch the change and routes to the login page. If you then login and the state changes to authenticated, nothing happens.Theolatheologian
Can you show class User? You can try to remove Equatable from the state just to be sure that you are not generating the same one(listener will not fire with the same state twice).Whittle
L
3

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.

Lea answered 15/2, 2021 at 11:27 Comment(0)
A
0

It looks like your AuthenticationRepository dispatches the AuthenticationEvent by listening to _authenticationRepository.userStream. I think the problem is that you create an instance of AuthenticationRepository earlier than AuthenticationBloc, this may cause the AuthenticationStatus change before the BLoC strats listening to it.

Though it would be better if you could provide AuthenticationRepository code.

Apophyllite answered 15/2, 2021 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.