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.