Async method return null in flutter - 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

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;
});
}

Flutter Firebase Auth Phone verification sms code issue

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
}

Using FirebaseAuth verifyPhoneNumber in 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;
}