i'm trying to send user a verification sms code using flutter FirebaseAuth instance, below is a snapshot of the code
pubspec.yml firebase_auth: ^1.2.0
Future<void> sendPhoneVerificationCode(String phoneNumber) async {
await FirebaseAuth.instance.verifyPhoneNumber(
autoRetrievedSmsCodeForTesting: phoneNumber,
phoneNumber: '+267$phoneNumber',
timeout: Duration(seconds: 60),
verificationCompleted: (phoneAuthCredential) {
this.smsCode = phoneAuthCredential.smsCode;
this.verificationId = phoneAuthCredential.verificationId;
logger.w(
'verification smsCode ${this.smsCode}',
);
},
verificationFailed: (error) {
if (error.code == 'invalid-phone-number') {
errorMessage = 'The provided phone number is not valid.';
} else {
errorMessage = error.message;
}
},
codeSent: (verificationId, [forceResendingToken]) {
this.verificationId = verificationId;
logger.w('verificationId is $verificationId');
},
codeAutoRetrievalTimeout: (String verificationId) {
this.verificationId = verificationId;
},
);
}
Instead of getting an sms code from verificationCompleted method, i'm getting phone number,
How can i get sms code sent to the user?
Here's an example repo:- demo repo
sendPhoneVerificationCode function :-
Future<void> _submitPhoneNumber() async {
/// NOTE: Either append your phone number country code or add in the code itself
/// Since I'm in India we use "+91 " as prefix `phoneNumber`
String phoneNumber = "+91 " + _phoneNumberController.text.toString().trim();
print(phoneNumber);
/// The below functions are the callbacks, separated so as to make code more readable
void verificationCompleted(AuthCredential phoneAuthCredential) {
print('verificationCompleted');
...
this._phoneAuthCredential = phoneAuthCredential;
print(phoneAuthCredential);
}
void verificationFailed(AuthException error) {
...
print(error);
}
void codeSent(String verificationId, [int code]) {
...
print('codeSent');
}
void codeAutoRetrievalTimeout(String verificationId) {
...
print('codeAutoRetrievalTimeout');
}
await FirebaseAuth.instance.verifyPhoneNumber(
/// Make sure to prefix with your country code
phoneNumber: phoneNumber,
/// `seconds` didn't work. The underlying implementation code only reads in `milliseconds`
timeout: Duration(milliseconds: 10000),
/// If the SIM (with phoneNumber) is in the current device this function is called.
/// This function gives `AuthCredential`. Moreover `login` function can be called from this callback
verificationCompleted: verificationCompleted,
/// Called when the verification is failed
verificationFailed: verificationFailed,
/// This is called after the OTP is sent. Gives a `verificationId` and `code`
codeSent: codeSent,
/// After automatic code retrival `tmeout` this function is called
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout,
); // All the callbacks are above
}
Related
Using flutter firebase auth and everything seems fine and working on most of the devices. But on one Infinix device the OTP is not getting sent and not even the function is getting executed. Also, the OTP is received after a very long time but yet no callback is called from auth.verifyPhoneNumber.
Here is the code
await auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
codeSent: (String verificationId, int? resendToken) {
log('codeSent Verification Id = $verificationId');
onCodeSent(verificationId);
},
verificationCompleted: (PhoneAuthCredential credential) async {
log('hello verification auto completed ${credential.smsCode}');
final user = await auth.signInWithCredential(credential);
if (user.user != null) {
final isNewUser = user.additionalUserInfo!.isNewUser;
if (isNewUser) {
debugPrint('Welcome New User: Adding to firestore!');
await addUser(UserData(
userId: user.user!.uid,
phone: user.user!.phoneNumber!,
createdAt: TimeUtils.nowMillis,
));
}
onAutoVerified(user.user!.uid);
}
},
verificationFailed: (FirebaseAuthException e) {
if (e.code == 'invalid-phone-number') {
log('The provided phone number is not valid.');
onVerificationFailed('The provided phone number is not valid.');
return;
}
log('Error = ${e.message}');
onVerificationFailed(
e.message ?? "Something went wrong! [code: s032]");
// Handle other errors
},
codeAutoRetrievalTimeout: (verificationId) {
log('TimeOut Verification Id = $verificationId');
},
);
No error, no log and no callback, even in the verbose mode. Only Ignoring header X-Firebase-Locale because its value was null
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;
},
);
}
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;
});
}
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;
}
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