I have a screen with text fields that when a button is pressed, they are sent to a cubit for a signin function. What im trying to do is that when an auth error occurs, id like to create a relevant snackbar or dialog, but im having trouble implementing it.
Text fields:
CustomLoginTextField(
controller: emailController,
hint: 'ENTER YOUR EMAIL',
onChanged: (value) {
context.read<SigninCubit>().emailChanged(value);
//print(context.read<SigninCubit>().state.email);
},
),
CustomLoginTextField(
controller: passwordController,
hint: 'ENTER YOUR PASSWORD',
onChanged: (value) {
context.read<SigninCubit>().passwordChanged(value);
//print(context.read<SigninCubit>().state.password);
// print(passwordController.text);
},
),
relevant button code:
if (tabController.index == 3) {
context.read<SigninCubit>().signInWithCredentials();
}
Cubit state:
part of 'signin_cubit.dart';
enum SigninStatus { initial, submitting, success, error }
class SigninState extends Equatable {
final String email;
final String password;
final SigninStatus status;
bool get isFormValid => email.isNotEmpty && password.isNotEmpty;
const SigninState({
required this.email,
required this.password,
required this.status,
});
factory SigninState.initial() {
return SigninState(
email: '',
password: '',
status: SigninStatus.initial,
);
}
@override
bool get stringify => true;
@override
List<Object> get props => [email, password, status];
SigninState copyWith({
String? email,
String? password,
SigninStatus? status,
}) {
return SigninState(
email: email ?? this.email,
password: password ?? this.password,
status: status ?? this.status,
);
}
}
Cubit:
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hero/repository/auth_repository.dart';
part 'signin_state.dart';
class SigninCubit extends Cubit<SigninState> {
final AuthRepository _authRepository;
SigninCubit({required AuthRepository authRepository})
: _authRepository = authRepository,
super(SigninState.initial());
void emailChanged(String value) {
emit(state.copyWith(email: value, status: SigninStatus.initial));
}
void passwordChanged(String value) {
emit(state.copyWith(password: value, status: SigninStatus.initial));
}
void signInWithCredentials() async {
try {
await _authRepository.signInWithEmailAndPassword(
email: state.email, password: state.password);
emit(
state.copyWith(status: SigninStatus.success),
);
} catch (e) {
emit(state.copyWith(status: SigninStatus.error));
}
}
}
Relevant auth function:
Future<User?> signInWithEmailAndPassword(
{required String email, required String password}) async {
try {
final credential = await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
final user = _userFromFirebaseUser(credential.user);
return user;
} catch (e) {
print(e);
}
}
In the auth function, how can i take that error and turn it into a snackbar/dialog displayed on the same screen as the text fields? Its currently printing the error, but that doesnt help the user experience very much. Thank you!
I would catch the error inside your SigninCubit
and emit
the corresponding state. Actually, you're already doing it with emit(state.copyWith(status: SigninStatus.error));
Then you can use a BlocListener<SigninCubit, SigninState>
and listen for state changes. Whenever your state changes and has a status
SigninStatus.error
you want to show the snackbar.
That would look something like this:
BlocListener<SigninCubit, SigninState>(
listener: (context, state) {
print('signinstate has changed');
if(state.status == SigninStatus.error) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Oops... Something went wrong')),
);
}
},
child: YourWidgetTree()
...
You can also use a BlocConsumer
which combines BlocListener
and BlocBuilder
.
One downside of this is that the snackbar would be displayed again every time anything of the state object changes as long as the status is SigninStatus.error
.
So you should either keep track of the displaying-snackbar-status by another variable like snackbarDisplayed
which you set true
after displaying and reset to false
when emitting a new error.
Or you can consume
the error by having a dedicated nullable error
field in your state which you set null
after displaying the snackbar.
In my latest project I use the latter approach. For that my cubit has the method
void clearError() => emit(state.copyWith(error: null));
And I use this method here:
void _consumeError(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Oops... Something went wrong')),
);
context.read<MyCubit>().clearError();
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.