Redirection to the home screen through OTP auto retrieval with Firebase

16 views Asked by At

I am using Flutter with Riverpod and Firebase.

My question is regarding the OTP authentication method and how it redirects to the home page in my app. I am handling routing with GoRouter: This is my app_router.dart:


@riverpod
GoRouter goRouter(GoRouterRef ref) {

  final authRepository = ref.watch(firebaseAuthRepositoryProvider);

  return GoRouter(
    initialLocation: "/",
    debugLogDiagnostics: true,
    refreshListenable: GoRouterRefreshStream(authRepository.authStateChanges()),

    redirect: (context, state) {
      final path = state.uri.path;
      final isLoggedIn = authRepository.currentUser != null;

      if(!isLoggedIn && path == "/"){
        return "/signIn";
      }
      //TO:DO: Redirection logic for checking if the user has completed registration and setup or not
      return null;
    },

    routes:[
      GoRoute(
        path: '/',
        name: AppRoute.home.name,
        pageBuilder: (context, state) => const NoTransitionPage(child: HomeScreen())
      ),
      GoRoute(
        path: '/signIn',
        name: AppRoute.signIn.name,
        pageBuilder: (context, state) => const NoTransitionPage(child: PhoneNumber()),
        routes: [
         GoRoute(
            path: 'otp/:phoneNo',
            name: AppRoute.otp.name,
            pageBuilder: (context, state) { 
              final phoneNo = state.pathParameters['phoneNo']!;
              return NoTransitionPage(child: OTPPage(phoneNumber: phoneNo));
            }
          ),
        ]
      ),
      
    ] 
  );
}

This is my firebaseAuthRepository:


class FireBaseAuthRepository implements AuthRepository{
  final  FirebaseAuth _firebaseAuth;
  FireBaseAuthRepository(this._firebaseAuth);
  String _verificationId = '';

  Future<void> initialize() async {
    await _firebaseAuth.setPersistence(Persistence.LOCAL);
  }


  AppUser? _userFromFirebase(User? user){
    if (user == null){
      return null;
    }
    return AppUser(
      uid: user.uid,
      phone: user.phoneNumber!,
    );
  }

  @override
  AppUser? get currentUser => _userFromFirebase(_firebaseAuth.currentUser);

  @override
  Stream<AppUser?> authStateChanges(){
    return _firebaseAuth.authStateChanges().map(_userFromFirebase);
  }

  @override
  Future<void> signInWithPhone({required String phoneNumber}) async {
    String phoneNumberWithCountryCode = '+91$phoneNumber';
    await _firebaseAuth.verifyPhoneNumber(
      phoneNumber: phoneNumberWithCountryCode,
      verificationCompleted: (PhoneAuthCredential credential) async {
        await _firebaseAuth.signInWithCredential(credential);
      }, 
      verificationFailed: (FirebaseAuthException e){
        throw Exception(e.message);
      }, 
      codeSent: (String verificationId, int? resendToken) async {
        _verificationId = verificationId;
      },
      codeAutoRetrievalTimeout: (verificationId){} ,
    );

  }

  @override
  Future<AppUser> verifyOtp({ required String otp}) async {
    String verificationId = '';
    if(_verificationId.isNotEmpty){
      verificationId = _verificationId;
    }
    AuthCredential credential = PhoneAuthProvider.credential(verificationId: verificationId, smsCode: otp);
    UserCredential user;
    try {
      user = await _firebaseAuth.signInWithCredential(credential);
    } catch (e) {
      throw "Invalid OTP. Please try again."; 
    }
    return _userFromFirebase(user.user)!;

  }

  @override
  Future<void> signOut() async {
    await _firebaseAuth.signOut();
  }
}

@Riverpod(keepAlive: true) 
FirebaseAuth firebaseAuthInstance(FirebaseAuthInstanceRef ref){
  return FirebaseAuth.instance;
}

@Riverpod(keepAlive: true)
AuthRepository firebaseAuthRepository(FirebaseAuthRepositoryRef ref){
  return FireBaseAuthRepository(ref.watch(firebaseAuthInstanceProvider));
}


@riverpod
Stream<AppUser?> authStateChanges(AuthStateChangesRef ref){
  return ref.watch(firebaseAuthRepositoryProvider).authStateChanges();
}

And this is my controller class that interacts with the repository:

part 'sign_in_controller.g.dart';

@riverpod
class SignInScreenController extends _$SignInScreenController {
  
  @override
  FutureOr<void> build() {
    // no-op
  }

  Future<void> signInWithPhone(phoneNumber) async {
    final authRepository = ref.watch(firebaseAuthRepositoryProvider);
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => authRepository.signInWithPhone(phoneNumber: phoneNumber)); 
  }

  Future<bool> verifyOtp(otp) async {
    final authRepository = ref.watch(firebaseAuthRepositoryProvider);
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => authRepository.verifyOtp(otp: otp));
    return state.hasError == false;
  }

  Future<void> signOut() async {
    final authRepository = ref.watch(firebaseAuthRepositoryProvider);
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => authRepository.signOut());
  }

}

The problem I am currently facing is that there is no clear way that I can think of to navigate from the OTP page to the main page through the verificationComplete method inside the signInWithPhone method of the Firebase auth repository class. I want to understand how to handle the navigation after auto-code retrieval.

0

There are 0 answers