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?