Ben
Ben

Reputation: 1231

flutter bloc: how to wait for an event the return some state data

I am trying to integrate flutter_login widget with bloc. Here is the sample code I am using

BlocProvider(
      create: (ctx) => UserAuthenticationPageBloc(ctx),
      child: BlocListener<UserAuthenticationPageBloc, UserAuthenticationPageState>(
        listener: (context, state) {
          // not used at the moment
        },
        child: BlocBuilder<UserAuthenticationPageBloc, UserAuthenticationPageState>(
          builder: (context, state) {
            final bloc = context.read<UserAuthenticationPageBloc>();

            return FlutterLogin(onLogin: (loginData) async {
              bloc.add(SignIn(loginData: loginData));
              return state.loginMessage;
            }, onRecoverPassword: (email) async {
              bloc.add(RecoverPassword(email: email));
              return state.recoverPasswordMessage;
            });
          },
        ),
      ),
    )

Here is the bloc file

class UserAuthenticationPageBloc extends Bloc<UserAuthenticationPageEvent, UserAuthenticationPageState> {
  UserAuthenticationPageBloc(BuildContext context) : super(const UserAuthenticationPageState()) {
    on<SignIn>((event, emit) {
      try {
        emit(state.copyWith(signInStatus: SignInStatus.loading));

        User user = User(); // need to be replaced by async http call

        final GlobalBloc globalBloc = BlocProvider.of<GlobalBloc>(context);

        globalBloc.add(GlobalSignIn(user: user));
        emit(state.copyWith(loginMessage: 'some error', signInStatus: SignInStatus.failure));
      } catch (_) {
        //emit(CovidError("Failed to fetch data. is your device online?"));
      }
    });
    on<RecoverPassword>((event, emit) {
    });
  }
}

What I would like to do is to add an event to bloc and then return a message. The flutter_login widget will show snack bar based on the message returned.

How can I wait for bloc event to finish before retrieving the loginMessage from the state? Or maybe I should not put the loginMessage in state?

Thank you

Upvotes: 3

Views: 5790

Answers (2)

Loren.A
Loren.A

Reputation: 5595

In most scenarios, these types of things can all be handled pretty easily with the BlocListener widget. Usually, in the UI you don't have to manually await anything coming from a bloc stream. This is one exception, where you're trying to utilize the built in UI elements of flutter_widget.

Since the onLogin callback requires a return of null on success or error message string on failure, you want to await the stream directly.

The syntax for that looks like this

 final authBloc = context.read<GlobalBloc>();
// fire the sign in event

// You know the first emitted state will be loading, so the next state is either success or failure

 await authBloc.stream.firstWhere(
              (state) => state.status != AuthStatus.loading,
            );

The whole widget would look something like this.

FlutterLogin(
          onLogin: (loginData) async {
            final authBloc = context.read<GlobalBloc>()
              ..add(
                SignIn(
                  email: loginData.name,
                  password: loginData.password,
                ),
              );

            await authBloc.stream.firstWhere(
              (state) => state.status != AuthStatus.loading,
            );

            return authBloc.state.status == AuthStatus.authenticated
                ? null
                : 'Login failed';
          },
          onRecoverPassword: (_) async {
            return null;
          },
        );

A couple things with what you shared.

  1. BlocBuilder widgets should not be placed directly below the BlocProvider. So in this case, to avoid that you could just create a new widget with the BlocBuilder wrapping a FlutterLogin. Although as you can see, in this case to use the built in snackbar, you don't need the BlocBuilder here.
  2. You're calling an event from the build method that makes network requests. This is pretty much always a bad idea, because we don't always have full control over how many times the build method is called and you run the risk of making way to many unintended network calls.
  3. Your UserAuthenticationPageBloc depends directly on GlobalBloc, which according to the docs should be avoided at all times. This is easily avoided with the BlocListener widget. I'm also not seeing why there are 2 separate blocs here at all just for login. So my example was simplified to just one Bloc class that handles login.

The gif below shows a 2 second wait and the bloc emitting an error state.

Edit

To answer your question in the comment:

I get that you want the auth info to be globally accessible, that's pretty common. Typically I would have my AuthBloc at the top of the widget tree along with any other Bloc that needs to be globally accessible. Not sure sure if you have other stuff going on in your GlobalBloc besides login, if not, just I'd just rename that to something like AuthBloc and get rid the separate one just for the login page.

If you do have other stuff going on in your GlobalBloc, I'd still just create an AuthBloc at the top of the widget tree and use that to manage the state of your login page, and that bloc would only be responsible for authentication. Either way, accessing blocs directly from other blocs makes testing much harder and is easily avoidable by utilizing the BlocListener widget. That is at least true when initially setting up blocs and general architecture. If you ignore all of that and when things get complex and you have lots of direct dependencies going on, it gets much harder to separate.

I would also avoid passing in BuildContext to a Bloc class. Even if you do need another Bloc, I would pass in the Bloc object directly (but again, don't do that). A bloc depending on BuildContext will also make testing harder.

About network calls in the build method, after re-reading your code this is my mistake. I see that you're just passing in callback to the onLogin param of the FlutterLogin widget, not actually making a network request in the build method, sorry for the confusion there.

Feel free to share your GlobalBloc if you're still looking for refactor help.

Upvotes: 9

laila nabil
laila nabil

Reputation: 321

You could try to pass onLogin function and onRecoverPassword function and state in separate parameters, and inside FlutterLogin check onloginMessage and recoverPasswordMessage not being null.

I also think you should you make yourself familiar with bloc by looking at different examples, I suggest the examples folder in the package itself https://github.com/felangel/bloc/tree/master/examples

Upvotes: 0

Related Questions