0

I am building a mobile application using Flutter. I am using BLoC for state management. I am using this library, https://pub.dev/packages/flutter_bloc. I app is emitting the event to the Bloc class. But the state is not updated in the screen or widget.

This is my LoginBloc class:

class LoginBloc extends Bloc<LoginEvent, LoginState> {

  LoginBloc() : super(LoginState.initial()) {
    on<LoginEvent>((event, emit) async {
      // yield the state here. check the event and then process the event and yield the state based on the result.
      if (event is Login) {
        var response = await ApiService.post(ApiEndpoints.login, {
          'email': event.email,
          'password': event.password
        });

        try {
          if (response.statusCode == 200) {
            // TODO: provide implementation
            var responseJson = jsonDecode(response.body);
            MeData meData = MeData.fromJson(responseJson['data']);
          } else {
            ApiError apiError = Utilities.parseApiError(response.body);
            emit(LoginState(event.email, event.password, false, apiError));
          }
        } catch (e) {
            var apiError = ApiError();
            apiError.setGenericErrorMessage("Something went wrong!");
            emit(LoginState(event.email, event.password, false, apiError));
        }
      }
    });
  }
}

This is my login_event.dart file.

abstract class LoginEvent {
  const LoginEvent();
}

// fields are the parameters to be passed to the service class in the bloc class.
class Login extends LoginEvent {
  final String email;
  final String password;

  const Login(this.email, this.password);
}

This is my login_state.dart file

class LoginState {
  String email = "";
  String password = "";
  bool isLoading = false;
  ApiError error = ApiError();
  String genericFormError = "";

  LoginState(String emailParam, String passwordParam, bool isLoadingParam, ApiError errorParam) {
    email = emailParam;
    password = passwordParam;
    isLoading = isLoadingParam;
    error = errorParam;
    genericFormError = error.getGenericErrorMessage();
  }

  static LoginState initial()
  {
    return LoginState("", "", false, ApiError());
  }
}

This is my login screen or widget

class _LoginPage extends State<LoginPage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  String? _email;
  String? _password;

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
          title: Text(widget.title)
      ),
      body: BlocListener<LoginBloc, LoginState>(
        listener: (context, state) {

        },
        child: BlocBuilder<LoginBloc, LoginState>(
            builder: (context, state) {
              return Center(
                  child: Form(
                    key: _formKey,
                    child: Column(
                      children: [
                        GenericFormError(errorMessage: state.genericFormError),
                        Padding(
                          padding: const EdgeInsets.all(10),
                          child: TextFormField(
                            decoration: InputDecoration(
                                labelText: "Email",
                                border: const OutlineInputBorder(),
                                errorText: state.error.getFieldError("email")
                            ),
                            onChanged: (value) => setState(() {
                              _email = value;
                            }),
                            validator: (value) {
                              if (value == null || value.isEmpty) {
                                return "Please enter email";
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.all(10),
                          child: TextFormField(
                            obscureText: true,
                            enableSuggestions: false,
                            autocorrect: false,
                            decoration: InputDecoration(
                                labelText: "Password",
                                border: const OutlineInputBorder(),
                                errorText: state.error.getFieldError("password")
                            ),
                            onChanged: (value) => setState(() {
                              _password = value;
                            }),
                            validator: (value) {
                              if (value == null || value.isEmpty) {
                                return "Please enter password";
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          child: SizedBox(
                            width: double.infinity,
                            height: 50,
                            child: ElevatedButton(
                              onPressed: () {
                                var isValid = _formKey.currentState!.validate();
                                if (isValid) {
                                  context.read<LoginBloc>().add(Login(_email.toString(), _password.toString()));
                                }
                              },
                              child: const Text('Login'),
                            ),
                          ),
                        ),
                      ],
                    ),
                  )
              );
            }
        ),
      ),
    );
  }
}

As you can see when the login button is clicked in the login screen/ widget, it is triggering the Login event.

This line in the LoginBloc was executed.

var apiError = ApiError();
            apiError.setGenericErrorMessage("Something went wrong!");
            emit(LoginState(event.email, event.password, false, apiError));

But the state is not updated in the login screen or widget.

The bloc listener method is in run as well. How can I fix it?

halfer
  • 19,824
  • 17
  • 99
  • 186
Wai Yan Hein
  • 13,651
  • 35
  • 180
  • 372

1 Answers1

0

You are currently not emitting any state if the response code is 200, so nothing would happen even if you weren't getting an error.

You should have an abstract Class of LoginState then define at least 3 classes ie. initial state of NotLoggedIn, LoginSuccess and LoginError that extend LoginState.

Your bloc should emit the corresponding states depending on each potential response and the BlocBuilder should respond to each state accordingly.

Loren.A
  • 4,872
  • 1
  • 10
  • 17
  • That is not entirely true. You can have only one state and still trigger a rebuild of your UI when you emit a new instance of this state. See: https://bloclibrary.dev/#/faqs?id=when-to-use-equatable. The rest of your answer is still valid tho. – puelo Oct 11 '21 at 22:16
  • Thanks for the correction @puelo. Noted for the future and I updated my response. – Loren.A Oct 12 '21 at 02:17