简体   繁体   中英

How to use bloc pattern correctly to signin user using Firebase Phone Auth?

I'm using Firebase Phone auth to sign in users, and flutter_bloc to manage the states.

If the user already exists, HomeScreen will be displayed.
Else if the user is new, Sign up process will start in which the user gives his name and email.
This name and email are saved in FirbaseAuth "User" DB and also in the Firestore under 'users' collection.
Next, the user has to select a city after which HomeScreen gets displayed with the products available in that city.

The problems I'm facing:-
The user gets logged out on restart.
Haven't implemented the else-if blocks correctly which leads to wrong states when phoneNumber, smsCode verification and smsCodeAutoRetrieval times out.

I'm planning to keep this project public which can help other beginners. Please see the code and help me in correcting the bloc pattern and suggest improvements. There's a video demo on github.

GitHubRepoLink

PhoneAuthBloc:-

import 'package:equatable/equatable.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:phone_auth_demo/models/phone_auth.dart';
import 'package:phone_auth_demo/repo/phone_auth_repo.dart';
part 'phone_auth_event.dart';
part 'phone_auth_state.dart';
class PhoneAuthBloc extends Bloc<PhoneAuthEvent, PhoneAuthState> {
  final PhoneAuthRepository _phoneAuthRepository;

  PhoneAuthBloc({
    required PhoneAuthRepository phoneAuthRepository,
  })  : _phoneAuthRepository = phoneAuthRepository,
        super(PhoneAuthInitial()) {
    on<PhoneAuthNumberVerified>(_phoneAuthNumberVerifiedToState);
    on<PhoneAuthCodeVerified>(_phoneAuthCodeVerifiedToState);
    on<PhoneAuthCodeAutoRetrevalTimeout>((event, emit) {
      emit(
        PhoneAuthCodeAutoRetrevalTimeoutComplete(
          event.verificationId,
        ),);
    });
    on<PhoneAuthCodeSent>((event, emit) {
      emit(
        PhoneAuthCodeSentSuccess(
          verificationId: event.verificationId,
        ),);
      emit(PhoneAuthNumberVerificationSuccess(
          verificationId: event.verificationId));
    });
    on<PhoneAuthVerificationFailed>((event, emit) {
      emit(
        PhoneAuthNumberVerficationFailure(
          event.message,
        ),);
    });
    on<PhoneAuthVerificationCompleted>((event, emit) async {
      emit(
        PhoneAuthCodeVerificationSuccess(
          uid: event.uid,
        ),);
    });
    on<PhoneAuthLoggedOut>((event, emit) async {
      emit(PhoneAuthLoading());
      await _phoneAuthRepository.unAuthenticate();
      emit(PhoneAuthInitial());
    });
  }
  void _phoneAuthNumberVerifiedToState(
      PhoneAuthNumberVerified event, Emitter<PhoneAuthState> emit) async {
    try {
      emit(PhoneAuthLoading());
      await _phoneAuthRepository.verifyPhoneNumber(
        phoneNumber: event.phoneNumber,
        onCodeAutoRetrievalTimeOut: _onCodeAutoRetrievalTimeout,
        onCodeSent: _onCodeSent,
        onVerificaitonFailed: _onVerificationFailed,
        onVerificationCompleted: _onVerificationCompleted,
      );
    } on Exception catch (e) {
      print('Exception occured while verifying phone number ${e.toString()}');
      emit(PhoneAuthNumberVerficationFailure(e.toString()));
    }
  }
 void _phoneAuthCodeVerifiedToState(
      PhoneAuthCodeVerified event, Emitter<PhoneAuthState> emit) async {
    try {
      emit(PhoneAuthLoading());
      PhoneAuthModel phoneAuthModel = await _phoneAuthRepository.verifySMSCode(
          smsCode: event.smsCode, verificationId: event.verificationId);
      emit(PhoneAuthCodeVerificationSuccess(uid: phoneAuthModel.uid));
    } on Exception catch (e) {
      print('Excpetion occured while verifying OTP code ${e.toString()}');
      emit(PhoneAuthCodeVerficationFailure(e.toString(), event.verificationId));
    }
  }
  void _onVerificationCompleted(PhoneAuthCredential credential) async {
    final PhoneAuthModel phoneAuthModel =
        await _phoneAuthRepository.verifyWithCredential(credential: credential);
 if (phoneAuthModel.phoneAuthModelState == PhoneAuthModelState.verified) {
      add(PhoneAuthVerificationCompleted(phoneAuthModel.uid));
    }
  }
 void _onVerificationFailed(FirebaseException exception) {
    print(
        'Exception has occured while verifying phone number: ${exception.toString()}');
    add(PhoneAuthVerificationFailed(exception.toString()));
  }
void _onCodeSent(String verificationId, int? token) {
    print(
        'Print code is successfully sent with verification id $verificationId and token $token');
 add(PhoneAuthCodeSent(
      token: token,
      verificationId: verificationId,
    ));
  }
 void _onCodeAutoRetrievalTimeout(String verificationId) {
    print('Auto retrieval has timed out for verification ID $verificationId');
    add(PhoneAuthCodeAutoRetrevalTimeout(verificationId));
  }
}

PhoneAuthState:

part of 'phone_auth_bloc.dart';
abstract class PhoneAuthState extends Equatable {
  const PhoneAuthState();
  @override
  List<Object?> get props => [];
}
class PhoneAuthInitial extends PhoneAuthState {}
class PhoneAuthStarted extends PhoneAuthState {}
class PhoneAuthLoading extends PhoneAuthState {}
class PhoneAuthError extends PhoneAuthState {}
class PhoneAuthNumberVerficationFailure extends PhoneAuthState {
  final String message;
  const PhoneAuthNumberVerficationFailure(this.message);
  @override
  List<Object> get props => [props];
}
class PhoneAuthNumberVerificationSuccess extends PhoneAuthState {
  final String verificationId;
  const PhoneAuthNumberVerificationSuccess({
    required this.verificationId,
  });
  @override
  List<Object> get props => [verificationId];
}
class PhoneAuthCodeSentSuccess extends PhoneAuthState {
  final String verificationId;
  const PhoneAuthCodeSentSuccess({
    required this.verificationId,
  });
  @override
  List<Object> get props => [verificationId];
}
class PhoneAuthCodeVerficationFailure extends PhoneAuthState {
  final String message;
  final String verificationId;
  const PhoneAuthCodeVerficationFailure(this.message, this.verificationId);
  @override
  List<Object> get props => [message];
}
class PhoneAuthCodeVerificationSuccess extends PhoneAuthState {
  final String? uid;
  const PhoneAuthCodeVerificationSuccess({
    required this.uid,
  });
  @override
  List<Object?> get props => [uid];
}
class PhoneAuthCodeAutoRetrevalTimeoutComplete extends PhoneAuthState {
  final String verificationId;
  const PhoneAuthCodeAutoRetrevalTimeoutComplete(this.verificationId);
  @override
  List<Object> get props => [verificationId];
}

phoneAuthEvents:

part of 'phone_auth_bloc.dart';
abstract class PhoneAuthEvent extends Equatable {
  const PhoneAuthEvent();
  @override
  List<Object?> get props => [];
}
class PhoneAuthNumberVerified extends PhoneAuthEvent {
  final String phoneNumber;
  const PhoneAuthNumberVerified({
    required this.phoneNumber,
  });
  @override
  List<Object> get props => [phoneNumber];
}
class PhoneAuthCodeSent extends PhoneAuthEvent {
  final String verificationId;
  final int? token;
  const PhoneAuthCodeSent({
    required this.verificationId,
    required this.token,
  });
  @override
  List<Object> get props => [verificationId];
}
class PhoneAuthCodeVerified extends PhoneAuthEvent {
  final String verificationId;
  final String smsCode;
  const PhoneAuthCodeVerified({
    required this.verificationId,
    required this.smsCode,
  });
  @override
  List<Object> get props => [smsCode];
}
class PhoneAuthCodeAutoRetrevalTimeout extends PhoneAuthEvent {
  final String verificationId;
  const PhoneAuthCodeAutoRetrevalTimeout(this.verificationId);
  @override
  List<Object> get props => [verificationId];
}
class PhoneAuthVerificationFailed extends PhoneAuthEvent {
  final String message;
  const PhoneAuthVerificationFailed(this.message);
  @override
  List<Object> get props => [message];
}
class PhoneAuthVerificationCompleted extends PhoneAuthEvent {
  final String? uid;
  const PhoneAuthVerificationCompleted(this.uid);
  @override
  List<Object?> get props => [uid];
}
class PhoneAuthLoggedOut extends PhoneAuthEvent {}

MyApp:-

lass MyApp extends StatelessWidget {
  const MyApp({
    Key? key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => PhoneAuthBloc(
        phoneAuthRepository: PhoneAuthRepository(
          phoneAuthFirebaseProvider: PhoneAuthFirebaseProvider(
            firebaseAuth: FirebaseAuth.instance,
          ),),),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const HomePage(),
      ),);}}

class HomePage extends StatelessWidget {
  const HomePage({super.key});
 @override
  Widget build(BuildContext context) {
    return BlocConsumer<PhoneAuthBloc, PhoneAuthState>(
      listener: (context, state) {
        if (state is PhoneAuthError) {
          Navigator.pushReplacement(
            context,
            MaterialPageRoute(
              builder: (_) => LoginScreen(),
            ),);}},
      builder: (context, state) {
        if (state is PhoneAuthInitial) {
          Future.delayed(Duration.zero, () {
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                builder: (_) => LoginScreen(),
              ),);});
        } else if (state is PhoneAuthLoading) {
          return const CircularProgressIndicator();
        } else if (state is PhoneAuthCodeVerificationSuccess) {
          return const HomeScreen();
        } else {
          return const Scaffold(
            body: CircularProgressIndicator(),
          );}
        return Container();
      },);}}

PhoneAuthProvider

class PhoneAuthFirebaseProvider {
  final FirebaseAuth _firebaseAuth;

  PhoneAuthFirebaseProvider({
    required FirebaseAuth firebaseAuth,
  }) : _firebaseAuth = firebaseAuth;

  Future<void> verifyPhoneNumber({
    required String mobileNumber,
    required onVerificationCompleted,
    required onVerificaitonFailed,
    required onCodeSent,
    required onCodeAutoRetrievalTimeOut,
  }) async {
    await _firebaseAuth.verifyPhoneNumber(
      phoneNumber: mobileNumber,
      verificationCompleted: onVerificationCompleted,
      verificationFailed: onVerificaitonFailed,
      codeSent: onCodeSent,
      codeAutoRetrievalTimeout: onCodeAutoRetrievalTimeOut,
      //timeout: const Duration(seconds: 5),
    );
  }

  Future<User?> loginWithSMSVerificationCode(
      {required String verificationId,
      required String smsVerficationcode}) async {
    final AuthCredential credential = _getAuthCredentialFromVerificationCode(
        verificationId: verificationId, verificationCode: smsVerficationcode);
    return await authenticationWithCredential(credential: credential);
  }

  Future<User?> authenticationWithCredential(
      {required AuthCredential credential}) async {
    UserCredential userCredential =
        await _firebaseAuth.signInWithCredential(credential);
    if (userCredential.user != null) {
      final uid = userCredential.user!.uid;
      final userSanp =
          await FirebaseFirestore.instance.collection('users').doc(uid).get();
      if (!userSanp.exists) {
        await FirebaseFirestore.instance.collection('users').doc(uid).set({
          'uid': uid,
          'phone': userCredential.user!.phoneNumber,
          'createdAt': DateTime.now(),
          'name': '',
          'email': '',
          'city': '',
        });
      }}
    return userCredential.user;
  }
 AuthCredential _getAuthCredentialFromVerificationCode(
      {required String verificationId, required String verificationCode}) {
    return PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: verificationCode,
    );
  }
Future<void> logout() async {
    await _firebaseAuth.signOut();
  }
}

I will try to say it just preface... that we have to create a event that will get triggered as soon as we launch the app (that will be called from main page) that checks if we have current user in cache... that by getting

mUserId = FirebaseAuth.instance.currentUser.;uid;

Then if we have user id we emit state as we want to else the login page.

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