简体   繁体   中英

Flutter: Show snackbar on firebaseAuth error

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM