Bloc: Authentication Based UI Component

Created on 27 Apr 2020  路  10Comments  路  Source: felangel/bloc

Describe the bug
I'm requiring a login for my users so they can access certain things in my app! for example, a user can't see a TextField() for putting comment if his not authenticated.

I have followed the article on the bloclibrary site and the login works great.

The issue is when the user first login then redirected to the home page and click somewhere to show post comments, he will not see the TextField!!

I need to hard restart the app or close it and reopen it again so the UI gets noticed that my user is Authenticated!!

The Ui Code:

BlocBuilder<AuthenticationBloc, AuthenticationState>(
  builder: (ctx, state) {
    if ( state is AuthenticationAuthenticated ) {
      return Directionality(
        textDirection: TextDirection.rtl,
        child: TextField(
          decoration: InputDecoration(
              hintText: '丕賰鬲亘 鬲毓賱賷賯...',
              prefixIcon: IconButton(
                  icon: FaIcon(FontAwesomeIcons.paperPlane),
                  onPressed: () {
                    SheetController.of(context).scrollTo(1000000);
                    BlocProvider.of<CommentsBloc>(context)
                        .add(AddComment('This is a comment'));
                  }),
              suffixIcon: FaIcon(FontAwesomeIcons.solidComment),
              contentPadding: EdgeInsets.all(10),
              border: OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.white))),
        )
      );
    }

    return RaisedButton(child: Text('not logged in'), onPressed: null,);
  },
)

The Auth Bloc:

@override
Stream<AuthenticationState> mapEventToState(
  AuthenticationEvent event,
) async* {

  if ( event is AppStarted ) {
    final bool hasToken = await userRepository.hasToken();

    if (hasToken) {
      yield AuthenticationAuthenticated();
    } else {
      yield AuthenticationUnauthenticated();
    }

  }

  if (event is LoggedIn) {
    yield AuthenticationLoading();
    await userRepository.persistToken(event.token, event.user);
    navigatorKey.currentState.pushReplacementNamed(TabsPage.routeName);
    yield AuthenticationAuthenticated();
  }

  if (event is LoggedOut) {
    yield AuthenticationLoading();
    await userRepository.deleteToken();
    yield AuthenticationUnauthenticated();
  }

  if (event is LoggedOutFromFacebook) {
    yield AuthenticationLoading();
    final FacebookLogin facebookLogin = FacebookLogin();
    await facebookLogin.logOut();
    await userRepository.deleteToken();
    yield AuthenticationUnauthenticated();
  }
}
question

Most helpful comment

Hey @heshaShawky 馃憢
As @enricobenedos kindly pointed out, the problem is you're not reusing the existing instance of AuthenticationBloc and are instead creating multiple instances. You should use BlocProvider.of<AuthenticationBloc>(context) to lookup the existing AuthenticationBloc that was provided higher up in the widget tree otherwise you are adding events to the new AuthenticationBloc and BlocBuilders and BlocListeners are listening for state changes in the old AuthenticationBloc.

Hope that helps and thanks so much @enricobenedos for taking the time to investigate the issue and provide a detailed answer 馃檹 I really appreciate it!

Closing this for now since it seems you were able to get everything working but feel free to add additional comments/questions and I'm happy to continue the conversation 馃憤

All 10 comments

Hi @heshaShawky 馃憢
Thanks for opening an issue!

Can you please share a link to a sample app which illustrates the issue you're having? It would be much easier for me to help if I'm able to reproduce the issue locally. Thanks! 馃憤

Here's the sample

If you click login nothing will change in real-time ( The UI ) and the BlocBuilder should change the UI based on what state is active.

I didn't know this issue and I just navigated from screen to another tell I start but some UI elements need to be logged in if you wanna use it.

Any updates on this?

Hey @heshaShawky, I checked your code and in my opinion your are making some errors:

  • You are passing a bloc to another bloc constructor, that otherwise is final and completely broke the library logic. Anyway I think it is completely wrong from the base
  • You are creating AuthenticationBloc two times

I can provide you the working code but your project need to be clean and fixed in a lot of places. To keep it working you need to change this code:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider.value(value: CommentsBloc(CommentsRepository()),),
        BlocProvider.value(
          value: UserBloc(
            userRepository: UserRepository(), 
            authenticationBloc: AuthenticationBloc(userRepository: UserRepository())
          ),
        )
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
......

to

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => CommentsBloc(CommentsRepository()),
        ),
        BlocProvider(
          create: (context) => UserBloc(
              userRepository: UserRepository(),
              authenticationBloc: BlocProvider.of<AuthenticationBloc>(context)),
        )
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
......

and

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository userRepository;
  final AuthenticationBloc authenticationBloc;
......

to

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository userRepository;
  AuthenticationBloc authenticationBloc;
......

I think that UserBloc is not necessary, because is AuthenticationBloc that need to manage the logic to login the user. To be more clear, this code:

final token = await userRepository.authenticate(
          username: event.username,
          password: event.password,
        );

should be done in AuthenticationBloc. Hope my explanation help you to start understanding your mistakes.

Hey @heshaShawky, I checked your code and in my opinion your are making some errors:

  • You are passing a bloc to another bloc constructor, that otherwise is final and completely broke the library logic. Anyway I think it is completely wrong from the base
  • You are creating AuthenticationBloc two times

Thanks a lot for sharing and explaining everything in details, I'm new in flutter so I'm kinda following the exact same examples on documentation for the library.

And here in login example on documentation did the same thing passing the AuthenticationBloc for the LoginBloc constructor. ( the login bloc in the example docs is the same user bloc in my code ) then adding the BlocProvider on the LoginForm level.

So if you could explain the best practices in what I have wrong, in general, I will be appreciated ( any references articles, videos ...etc )

Thanks a lot for sharing and explaining everything in details, I'm new in flutter so I'm kinda following the exact same examples on documentation for the library.
And here in login example on documentation did the same thing passing the AuthenticationBloc for the LoginBloc constructor. ( the login bloc in the example docs is the same user bloc in my code ) then adding the BlocProvider on the LoginForm level.

No problem, I haven never see the linked example before and you are right telling me that your app is applying the same pattern. I've not verified that is perfectly equal but it seems very similar.

So if you could explain the best practices in what I have wrong, in general, I will be appreciated ( any references articles, videos ...etc )

I think that the library documentation already explain really well how to use it. @felangel make also some very useful articles on Medium.
At this point it is better to wait someone that can explain better where is the main problem. Anyway If I have some time I will continue investigating on your project to understand if the error is somewhere else.

No problem, I haven never see the linked example before and you are right telling me that your app is applying the same pattern. I've not verified that is perfectly equal but it seems very similar.

You modifications btw work perfectly, and with more investigation, I found this what causes me the problem

from my code above:

return MultiBlocProvider(
  providers: [
    BlocProvider.value(value: CommentsBloc(CommentsRepository()),),
    BlocProvider.value(
      value: UserBloc(
        userRepository: UserRepository(), 
        authenticationBloc: AuthenticationBloc(userRepository: UserRepository()) // this line here
      ),
    )
  ],
  child: ...
);

The working version

return MultiBlocProvider(
  providers: [
    BlocProvider.value(value: CommentsBloc(CommentsRepository()),),
    BlocProvider.value(
      value: UserBloc(
        userRepository: UserRepository(), 
        authenticationBloc: BlocProvider.of<AuthenticationBloc>(context) // this line here 
      ),
    )
  ],
  child: ...

  ),

So my question what the difference by using BlocProvider.of<AuthenticationBloc>(context) than AuthenticationBloc(userRepository: UserRepository()) that I were using it?

The reason is simply, using this code

BlocProvider<AuthenticationBloc>(
      create: (context) {
        return AuthenticationBloc(userRepository: userRepository)
          ..add(AppStarted());
      },
      child: MyApp(),
    ),
  );

you are providing AuthenticationBloc to the widget tree, this mean that if you want to use it in a child widget you can simply obtain it calling BlocProvider.of<AuthenticationBloc>(context).
That means that MyApp() widget and all its children widgets can retrieve AuthenticationBloc and use it.
Your mistake is that when you are creating UserBloc() and you need to pass the AuthenticationBloc instance, in order to use it later, you are creating a new AuthenticationBloc. This is the error, you need to pass the already created instance that you inherit from MyApp() and to do this you simply ask to BlocProvider to get this instance using this BlocProvider.of<AuthenticationBloc>(context).

And forget this, sometimes works with a lot of project make some jokes 馃榿

and
class UserBloc extends Bloc {
final UserRepository userRepository;
final AuthenticationBloc authenticationBloc;
......
to
class UserBloc extends Bloc {
final UserRepository userRepository;
AuthenticationBloc authenticationBloc;
......

And as the documentation tell you, avoid to use this extension method if you don't have to pass an existing bloc to a new route/widget because in this case BlocProvider does not dispose the bloc for you

In some cases, BlocProvider can be used to provide an existing bloc to a new portion of the widget tree. This will be most commonly used when an existing bloc needs to be made available to a new route. In this case, BlocProvider will not automatically close the bloc since it did not create it.

BlocProvider.value(
      value: UserBloc(
        userRepository: UserRepository(), 
        authenticationBloc: BlocProvider.of<AuthenticationBloc>(context) // this line here 
      ),
    )

use this

to
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => CommentsBloc(CommentsRepository()),
),
BlocProvider(
create: (context) => UserBloc(
userRepository: UserRepository(),
authenticationBloc: BlocProvider.of(context)),
)
],
child: MaterialApp(
title: 'Flutter Demo',
......

Hey @heshaShawky 馃憢
As @enricobenedos kindly pointed out, the problem is you're not reusing the existing instance of AuthenticationBloc and are instead creating multiple instances. You should use BlocProvider.of<AuthenticationBloc>(context) to lookup the existing AuthenticationBloc that was provided higher up in the widget tree otherwise you are adding events to the new AuthenticationBloc and BlocBuilders and BlocListeners are listening for state changes in the old AuthenticationBloc.

Hope that helps and thanks so much @enricobenedos for taking the time to investigate the issue and provide a detailed answer 馃檹 I really appreciate it!

Closing this for now since it seems you were able to get everything working but feel free to add additional comments/questions and I'm happy to continue the conversation 馃憤

Thanks a lot for the detailed answer, I hope all best for your @enricobenedos.

And also for @felangel ( Thank you not enough ) as well for developing this incurable amazing library to simplify the BLOC pattern as much as possible, as well for not been annoyed that I'm asking questions here that in the majority ( actually all of my questions tell now XD ) no bugs/problems related in the library itself but my code.

Was this page helpful?
0 / 5 - 0 ratings