Using FirebaseAuth verifyPhoneNumber in flutter? - flutter

Hey I am trying to use Firebase Auth verifyPhoneNumber in Flutter.
I am constantly getting my msg in onTap function as null. I tried declaring a variable in registerToFb function but I am not able to update its value. How do I solve it?
Following is the function from where I am calling the firebaseAuth.verifyPhoneNymber() method.
onTap: () async {
setState(() => isLoading = true);
String msg = await _auth.registerToFb(
"+91$truephoneNumber", context);
print(msg);
},
Following is where I am defining my function.
class AuthenticationService {
final FirebaseAuth _firebaseAuth;
AuthenticationService(this._firebaseAuth);
Future registerToFb(String phoneNumber, BuildContext context) async {
String result = " ";
await _firebaseAuth.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(seconds: 30),
verificationCompleted: (PhoneAuthCredential credential) async {
UserCredential authresult =
await _firebaseAuth.signInWithCredential(credential);
User user = authresult.user;
_getUserFromFirebase(user);
result = "signedUp";
},
verificationFailed: (FirebaseAuthException e) {
String error = e.code == 'invalid-phone-number'
? "Invalid number. Enter again."
: "Can Not Login Now. Please try again.";
result = error;
},
codeSent: (String verificationId, int resendToken) {
result = "verified";
},
codeAutoRetrievalTimeout: (String verificationId) {},
);
}
return result;
}
Help me, please. Thanks

The problem is that you aren't returning anything in your function. And even when you try to save a value from inside the callbacks, the future from verifyPhoneNumber resolves before those callbacks get called.
You need to be awaiting the right thing. You shouldn't await the future returned from verifyPhoneNumber. You should await a new future that doesn't resolve until you get a result from one of the callbacks. This means you need to create your own Future which you can manually resolve specifically when you need it to. You can do this with a Completer:
Future<String> registerToFb(String phoneNumber, BuildContext context) {
final completer = Completer<String>();
_firebaseAuth.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(seconds: 30),
verificationCompleted: (PhoneAuthCredential credential) async {
UserCredential authresult =
await _firebaseAuth.signInWithCredential(credential);
User user = authresult.user;
_getUserFromFirebase(user);
completer.complete("signedUp");
},
verificationFailed: (FirebaseAuthException e) {
String error = e.code == 'invalid-phone-number'
? "Invalid number. Enter again."
: "Can Not Login Now. Please try again.";
completer.complete(error);
},
codeSent: (String verificationId, int resendToken) {
completer.complete("verified");
},
codeAutoRetrievalTimeout: (String verificationId) {
completer.complete("timeout");
},
);
return completer.future;
}

Related

Firebase Phone Authentication Error In Flutter - The sms code has expired. Please re-send the verification code to try again

I implemented Firebase phone authentication (OTP Login) successfully and it was working fine. However, I had to change my keystore alias and generate a new keystore to update the app in Play Store.
Since then, I am facing this issue where it says the otp is wrong every time. Even when I copy-paste the otp from message, it throws the error every time. It is also behaving weird, for some phone numbers, the otp works successfully but for some others, it doesn't. Any little help will be appreciated a lot.
It is important to note that OTP Authentication is working fine in emulators but the error shows up when installed in a real device.
Here's my code snippet below -
String verficationIdRecieved = "";
int? _resendToken;
void verifyNumber() {
auth.verifyPhoneNumber(
phoneNumber: "+880" + phoneNoController.text,
verificationCompleted: (PhoneAuthCredential credential) async {
await auth.signInWithCredential(credential).then((value) async {
print("verficationCompleted : Logged in");
// Get.offAll(() => OtpVerifyCodeScreen);
});
},
verificationFailed: (FirebaseAuthException exception) {
LoadingDialog().dismiss();
print(exception.message);
getPrefix.Get.snackbar("Error verifyNumber",
"Please check if your phone number is right. ${exception.message}");
},
codeSent: (String verficationId, int? resendToken) async {
verficationIdRecieved = verficationId;
_resendToken = resendToken;
otpCodeVisible = true;
setState(() {
timedButtonActtive = true;
});
LoadingDialog().dismiss();
},
timeout: Duration(seconds: 60),
forceResendingToken: _resendToken,
codeAutoRetrievalTimeout: (String verificationId) {
// ADDED LATER https://stackoverflow.com/questions/61132218/resend-otp-code-firebase-phone-authentication-in-flutter
// REMOVE IF ERROR
verficationIdRecieved = verificationId;
},
);
}
var firebaseToken;
void verifyCode(BuildContext context) async {
LoadingDialog().show(context);
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verficationIdRecieved, smsCode: codeController.text);
try {
await auth.signInWithCredential(credential).then((value) {
// print("\n\n User UID should be ${value.user!.uid}");
print("verifyCode : Login successfull");
value.user!.getIdToken().then((value) => {
firebaseToken = value,
print("This is the firebase token == $firebaseToken"),
verifyOtp(
mobileNo: phoneNoController.text,
otp: codeController.text,
ftoken: firebaseToken)
});
final snackBar = SnackBar(content: Text("Login Successful"));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
// getPrefix.Get.offAll(() => SetUsernameScreen());
});
} catch (e) {
final snackBar = SnackBar(
content: Text(
"The sms code has expired or you entered a wrong code. Please re-send the code to try again."));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
I think I know your problem.
Android automatically confirms the user as soon as it receives the code before the user clicks the confirmation button.
Saying that you do not have this problem in the emulator is why you receive the code on the real phone.
If you want to manually verify the user, you have two options:
One is to set the timeout to 5 seconds. (So ​​that Android can not automatically verify the user).
The second solution is not to do anything in the verificationCompleted method because now Android is using this method to confirm the user. (My suggestion is to use this way.)
I hope this will solve your problem.
void verifyNumber() {
auth.verifyPhoneNumber(
phoneNumber: "+880" + phoneNoController.text,
verificationCompleted: (PhoneAuthCredential credential) async {
// await auth.signInWithCredential(credential).then((value) async {
// print("verficationCompleted : Logged in"); //Here
// // Get.offAll(() => OtpVerifyCodeScreen);
// });
},
verificationFailed: (FirebaseAuthException exception) {
LoadingDialog().dismiss();
print(exception.message);
getPrefix.Get.snackbar("Error verifyNumber",
"Please check if your phone number is right. ${exception.message}");
},
codeSent: (String verficationId, int? resendToken) async {
verficationIdRecieved = verficationId;
_resendToken = resendToken;
otpCodeVisible = true;
setState(() {
timedButtonActtive = true;
});
LoadingDialog().dismiss();
},
timeout: Duration(seconds: 60),
forceResendingToken: _resendToken,
codeAutoRetrievalTimeout: (String verificationId) {
// ADDED LATER https://stackoverflow.com/questions/61132218/resend-otp-code-firebase-phone-authentication-in-flutter
// REMOVE IF ERROR
verficationIdRecieved = verificationId;
},
);
}

Getting NoSuchMethodError: The getter 'uid' was called on null flutter

I'm pretty new to flutter firebase and I am getting this error NoSuchMethodError: The getter 'uid' was called on null flutter. I have searched and looked very carefully but left scratching my head only where is the error. Over on my condition I'm checking if user is not equal to null then save it but its not happening.
Here's my phone authentication file code which should save the user as well but its not doing it right now because of the null issue
class PhoneAuthService {
FirebaseAuth auth = FirebaseAuth.instance;
User user = FirebaseAuth.instance.currentUser;
CollectionReference users = FirebaseFirestore.instance.collection('users');
Future<void> addUser(context, uid) async {
final QuerySnapshot result = await users.where('uid', isEqualTo: uid).get();
List<DocumentSnapshot> document = result.docs;
if (document.length > 0) {
Navigator.pushReplacementNamed(context, LocationScreen.id);
} else {
return users.doc(user.uid).set({
'uid': user.uid,
'mobile': user.phoneNumber,
'email': user.email,
}).then((value) {
Navigator.pushReplacementNamed(context, LocationScreen.id);
}).catchError((error) => print("Failed to add user: $error"));
}
}
Future<void> verifyPhonenumber(BuildContext context, number) async {
final PhoneVerificationCompleted verificationCompleted =
(PhoneAuthCredential credential) async {
await auth.signInWithCredential(credential);
};
final PhoneVerificationFailed verificationFailed =
(FirebaseAuthException e) {
if (e.code == 'invalid-phone-number') {
print('The phone number is not valid');
}
print("The error is ${e.code}");
};
final PhoneCodeSent codeSent = (String verId, int resendToken) async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OTPScreen(
number: number,
verId: verId,
)));
};
try {
auth.verifyPhoneNumber(
phoneNumber: number,
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
timeout: const Duration(seconds: 60),
codeAutoRetrievalTimeout: (String verificationId) {
print(verificationId);
});
} catch (e) {
print("Error ${e.toString()}");
}
}
}
This is where I'm getting the user otp and proceeding it further
Future<void> phoneCredential(BuildContext context, String otp) async {
FirebaseAuth _auth = FirebaseAuth.instance;
try {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: widget.verId, smsCode: otp);
final User user = (await _auth.signInWithCredential(credential)).user;
if (user != null) {
_services.addUser(context, user.uid);
Navigator.pushReplacementNamed(context, LocationScreen.id);
} else {
print("Login Process Failed ");
if (mounted) {
setState(() {
error = 'Login Failed';
});
}
}
} catch (e) {
print(e.toString());
if (mounted) {
setState(() {
error = 'Invalid OTP';
});
}
}
}
If anyone understood where I'm doing wrong please help I'm stuck for 3 days on it now.

Do not want to complete the Future unless the verifyPhoneNumber method is complete

I want to verify a phone number of a user and then only allow that user to signup. But for this when I call verifyPhoneNumber even if I apply await it does not wait till the verification is completed. it executes the next line. So what I want to do is unless the either of verificationCompleted, verificationFailed, codeAutoRetrievalTimeout completes I want to wait and then only executes the code which creates the document for the user in database.
Any help will be appreciated!!!
static Future signUp(BuildContext context, String name, String phoneNumber,
String email, String gender) async {
await auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (credential) async {
await auth.signInWithCredential(credential);
},
verificationFailed: _verificationFailed,
codeSent: (String verificationId, int resendToken) async{
await _codeSent(verificationId, resendToken, context);
},
codeAutoRetrievalTimeout: (String timeout) {});
if (auth.currentUser != null) {
await DB.users.doc(auth.currentUser.uid).set({
name: name,
phoneNumber: phoneNumber,
email: email,
});
}
return;
}
Thanks!!
FirebaseAuth.verifyPhoneNumber starts verification. I expect that its returned Future completes when the verification request has been issued, not when recipient has completed verification.
If you really want a Future that can be awaited, you can use a Completer. One way to use it to transform your set of completion callbacks:
var verificationCompleter = Completer<PhoneAuthCredential>();
await auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (credential) {
verificationCompleter.complete(credential);
},
verificationFailed: (error) {
verificationComplete.completeError(error);
},
codeSent: (String verificationId, int resendToken) async{
await _codeSent(verificationId, resendToken, context);
},
codeAutoRetrievalTimeout: (String id) {
verificationComplete.completeError(
TimeoutException('Verification request timed out'),
);
},
);
PhoneAuthCredential credential;
try {
credential = await verificationComplete.future;
} on FirebaseAuthException catch (e) {
_verificationFailed(error);
return;
} on TimeoutException {
// Handle a timeout error however you like.
// ...
return;
}
await auth.signInWithCredential(credential);
if (auth.currentUser != null) {
// ...
}
You can you .then clause. so in your code you can attach it like below. the value parameter holds any information passed from the code awaited for the then clause.
static Future signUp(BuildContext context, String name, String phoneNumber,
String email, String gender) async {
await auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (credential) async {
await auth.signInWithCredential(credential);
},
verificationFailed: _verificationFailed,
codeSent: (String verificationId, int resendToken) async{
await _codeSent(verificationId, resendToken, context);
},
codeAutoRetrievalTimeout: (String timeout) {}).then((value){
//Code you want to execute after the above is completed.
if (auth.currentUser != null) {
await DB.users.doc(auth.currentUser.uid).set({
name: name,
phoneNumber: phoneNumber,
email: email,
});
}
return;
});
}

Async method return null in flutter

I am trying to fetch the verificationid from APIClient class to login screen.
**In Login.dart**
FetchVerificationID() {
ApiProviderObj.FetchFirebaseVeriID().then((value) {
print(value); // This always get null
});
}
In APIPROVIDER.dart class
Future<String> FetchFirebaseVeriID() async {
final PhoneCodeSent smsOTPSent = (String verId, [int forceCodeResend]) {
verificationId = verId;
return verId; // This is what i am expecting the return value. It take some time to reach here but the method return value before reaching here
};
try {
await _auth.verifyPhoneNumber(
phoneNumber: '+91 93287 46333', // PHONE NUMBER TO SEND OTP
codeAutoRetrievalTimeout: (String verId) {
//Starts the phone number verification process for the given phone number.
//Either sends an SMS with a 6 digit code to the phone number specified, or sign's the user in and [verificationCompleted] is called.
verificationId = verId;
},
codeSent:
smsOTPSent, // WHEN CODE SENT THEN WE OPEN DIALOG TO ENTER OTP.
timeout: const Duration(seconds: 20),
verificationCompleted: (AuthCredential phoneAuthCredential) {
print('phoneAuthCredential => ${phoneAuthCredential}');
return verId;
},
verificationFailed: (AuthException exceptio) {
return "Error";
});
} catch (e) {
return "Error";
}
}
You should have the return statement at the end of the method.
I don't really know what is the cause of this, but I had an issue like you with a FutureBuilder.
In the async method I have the return values, but the return value was null.
Future<String> FetchFirebaseVeriID() async {
String returnVar; //Create return variable
final PhoneCodeSent smsOTPSent = (String verId, [int forceCodeResend]) {
verificationId = verId;
return verId;
};
try {
await _auth.verifyPhoneNumber(
phoneNumber: '+91 93287 46333', // PHONE NUMBER TO SEND OTP
codeAutoRetrievalTimeout: (String verId) {
//Starts the phone number verification process for the given phone number.
//Either sends an SMS with a 6 digit code to the phone number specified, or sign's the user in and [verificationCompleted] is called.
verificationId = verId;
},
codeSent:
smsOTPSent, // WHEN CODE SENT THEN WE OPEN DIALOG TO ENTER OTP.
timeout: const Duration(seconds: 20),
verificationCompleted: (AuthCredential phoneAuthCredential) {
print('phoneAuthCredential => ${phoneAuthCredential}');
returnVar = verId; //Set the return value
},
verificationFailed: (AuthException exceptio) {
returnVar = "Error"; //Set the return value
});
} catch (e) {
returnVar = "Error"; //Set the return value
}
return returnVar; //And return the value
}
I think you can try to use the Completer class to complete your Future when verificationCompleted gets called and verId is available.
Like so:
Future<String> FetchFirebaseVeriID() async {
// ...
final completer = new Completer();
try {
await _auth.verifyPhoneNumber(
// ...
verificationCompleted: (AuthCredential phoneAuthCredential) {
print('phoneAuthCredential => ${phoneAuthCredential}');
completer.complete(verId);
},
verificationFailed: (AuthException exceptio) {
completer.completeError("Error");
});
} catch (e) {
completer.completeError("Error");
}
return completer.future;
}
https://api.dart.dev/stable/2.7.1/dart-async/Completer-class.html

Flutter : phone verification firebase_auth get error when submit wrong Code

why i'm getting error and stuck when i type wrong code in textfield?
void _signInWithPhoneNumber(String smsCodes) async {
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCodes,
);
FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseUser user =
await _auth.signInWithCredential(credential).then((user) {
Navigator.of(context).pop();
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SuksesPhone()));
}).catchError((e) {
print(e);
Navigator.of(context).pop();
setState(() {
canClick = true;
});
});
}
but when i type correct code, no error.
the error is like this.
Not 100% sure, but I think it should be like this. When using await, you shouldn't use then.
void _signInWithPhoneNumber(String smsCodes) async {
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCodes,
);
FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseUser user =
await _auth.signInWithCredential(credential).catchError((onError{
//do something
}));
//check user
if(user){
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SuksesPhone()));
}
}