I'm trying to create an auth service and I want to return the verificationId from the custom method. However, calling this method throws the null check exception because it doesn't wait for the Future to complete before returning.
Future<String> sendPhoneVerification({
required String phoneNumber,
}) async {
String? result;
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: '+1$phoneNumber',
verificationCompleted: (
PhoneAuthCredential credential,
) {
result = credential.verificationId;
},
verificationFailed: (e) {
if (e.code == 'invalid-phone-number') {
throw InvalidPhoneNumberAuthException();
} else if (e.code == 'too-many-requests') {
throw TooManyRequestsAuthException();
} else {
throw GenericAuthException();
}
},
codeSent: (verificationId, resendToken) {
print('ver_id $verificationId');
result = verificationId;
},
codeAutoRetrievalTimeout: (_) {},
);
print('This is the result $result');
return result!;
}
Here is the output in the terminal.
flutter: This is the result null
flutter: ver_id <ver_id>
Please add this property timeout: const Duration(seconds: 60), in the verifyPhoneNumber() method
I figured out the solution. I found out the verifyPhoneNumber method returns a future but the implementation doesn't await that async call. I used a [completer][1] to return a future.
Future<String> sendPhoneVerification({required String phoneNumber}) async {
Completer<String> result = Completer();
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: '+1$phoneNumber',
verificationCompleted: (
PhoneAuthCredential credential,
) {
result.complete(credential.verificationId);
},
verificationFailed: (e) {
if (e.code == 'invalid-phone-number') {
result.completeError(InvalidPhoneNumberAuthException());
} else if (e.code == 'too-many-requests') {
result.completeError(TooManyRequestsAuthException());
} else {
result.completeError(GenericAuthException());
}
},
codeSent: (verificationId, resendToken) {
result.complete(verificationId);
},
codeAutoRetrievalTimeout: (_) {},
);
return result.future;
}
Related
how to put the user id in this code when I am generating a to-do list and i need to retrieve the tasks that a certain user?
Future<void> create(String todo, String description) async {
try {
await firestore.collection("Todo").add({
'todo': todo,
'description': description,
'timestamp': FieldValue.serverTimestamp()
});
} catch (e) {
print(e);
}
}
i solved my issue
Future<void> create(String todo, String description) async {
String? Uid= FirebaseAuth.instance.currentUser!.uid;
print(Uid);
print(FirebaseAuth.instance.currentUser!.uid);
try {
await firestore
.collection("TodoList")
.doc(Uid)
.collection("Todo")
.add({
'todo': todo,
'description': description,
'timestamp': FieldValue.serverTimestamp()
});
} catch (e) {
print(e);
}
}
Future<void> signUpController(email, password, username, phone) async {
print("$email,$password,$username,$phone");
try {
CommanDialog.showLoading();
final credential =
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email.trim(),
password: password,
);
print(credential);
CommanDialog.hideLoading();
try {
CommanDialog.showLoading();
var response =
await FirebaseFirestore.instance.collection('userlist').add({
'user_id': credential.user!.uid,
'user_name': username,
'phone': phone,
'password': password,
'joindate': DateTime.now().millisecondsSinceEpoch,
'email': email
});
print("response:: ${response.toString()}");
CommanDialog.hideLoading();
Get.back();
} catch (execption) {
CommanDialog.hideLoading();
print("error saving data ${execption}");
}
Get.back();
} on FirebaseAuthException catch (e) {
CommanDialog.hideLoading();
if (e.code == 'weak-password') {
CommanDialog.showErrorDialog(
description: "The password provided is too weak.");
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
CommanDialog.showErrorDialog(
description: "The account already exists for that email.");
print('The account already exists for that email.');
}
} catch (e) {
CommanDialog.hideLoading();
CommanDialog.showErrorDialog(description: "something went wrong");
print(e);
}
}
i wanted to wait till firebase auth retrive verification_id and then return function
but in current code variable value newVerificationId is getting return first as error without updating from firebase
Future<String> phoneAuthontication(
phoneNumberController,
) async {
String newVerificationId = "error";
try {
await auth.verifyPhoneNumber(
phoneNumber: phoneNumberController.text,
verificationCompleted: (_) {},
verificationFailed: (e) {
print(e);
},
codeSent: (String verificationId, int? token) {
print("====" + verificationId);
newVerificationId = verificationId;
},
codeAutoRetrievalTimeout: (e) {
print(e);
});
} catch (e) {
print(e);
}
print("---" + newVerificationId);
return newVerificationId;
}
I try to make some unit tests on a login funtion in flutter and I have an error that I can't fix.
This is the test:
test('Register (success)', () async {
var auth = AuthService();
var result = await auth.login("test", "123456");
var json = jsonDecode(result.toString());
expect(json['success'], true);
});
This is the function called by the test:
login(name, password) async {
if (name.toString().isEmpty || password.toString().isEmpty) {
return "Error: empty field";
}
return await dio.post('https://itsmilife.herokuapp.com/authenticate',
data: {"name": name, "password": password},
options: Options(contentType: Headers.formUrlEncodedContentType))
}
This is the function that sent the informations:
authenticate: function(req, res) {
User.findOne({
name: req.body.name
}, function (err, user) {
if (err) throw err
if (!user) {
res.status(403).send({success: false, msg: 'Authentication Failed, User not found'})
} else {
user.comparePassword(req.body.password, function (err, isMatch) {
if (isMatch && !err) {
var token = jwt.encode(user, config.secret)
res.json({success: true, token: token, user: user})
}
else {
return res.status(403).send({success: false, msg: 'Authentication failed wrong password'})
}
})
}
})
},
I have the error when I launch the test:
NoSuchMethodError: The method '[]' was called on null.
Receiver: null
Tried calling: []("success")
dart:core Object.noSuchMethod
test\widget_test.dart 24:16 main.<fn>
I don't understand how to fix the probleme.
Try something like this:
Future<bool> login(name, password) async {
if (name.toString().isEmpty || password.toString().isEmpty) {
return false;
}
var responseFromServer = await dio.post(
'https://itsmilife.herokuapp.com/authenticate',
data: {"name": name, "password": password},
options: Options(contentType: Headers.formUrlEncodedContentType));
if (responseFromServer == null || responseFromServer.statusCode != 200) {
return false;
}
return true;
}
Also, make your test like:
test('Register (success)', () async {
var auth = AuthService();
var result = await auth.login("test", "123456");
expect(result, true);
});
This question already has answers here:
How to Async/await in List.forEach() in Dart
(7 answers)
Closed 2 years ago.
I'm using firebase cloud firestore
inside a Future function I have this
try {
categories.forEach((element) async {
await FirebaseFirestore.instance.collection('Categories').add({
'name': element[0],
'imageUrl': element[1],
});
print('done');
});
print('complete');
} catch (e) {
CoolAlert.show(
context: context,
type: CoolAlertType.error,
content: Text(e),
text: "Upload Failed",
onConfirmBtnTap: () {
Navigator.pop(context);
Navigator.pop(context);
});
}
'completed' printed before 'done'
how to make it the opposite?
how to await for the forEach function to end first then proceed
and even if I moved print('complete'); after the whole try catch block it doesn't work either
so is there a way to wait try catch block?
You can use Future.foreach OR Future.doWhile
Future.doWhile :
int index = 0;
try {
Future.doWhile(() {
if (index < categories.length) {
await FirebaseFirestore.instance.collection('Categories').add({
'name': categories[index][0],
'imageUrl': categories[index][1],
});
print('done');
index++;
return true;
} else {
print('complete');
return false;
}
});
} catch (e) {
CoolAlert.show(
context: context,
type: CoolAlertType.error,
content: Text(e),
text: "Upload Failed",
onConfirmBtnTap: () {
Navigator.pop(context);
Navigator.pop(context);
});
}
Future.foreach:
try {
Future.forEach(categories,(element) async {
await FirebaseFirestore.instance.collection('Categories').add({
'name': element[0],
'imageUrl': element[1],
});
print('done');
});
print('complete');
} catch (e) {
CoolAlert.show(
context: context,
type: CoolAlertType.error,
content: Text(e),
text: "Upload Failed",
onConfirmBtnTap: () {
Navigator.pop(context);
Navigator.pop(context);
});
}
I'm trying to implement Firebase phone authorization using Flutter Bloc pattern.
I have the following code
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:firebase_auth/firebase_auth.dart';
import './bloc.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final FirebaseAuth _auth = FirebaseAuth.instance;
#override
AuthState get initialState => AuthNotStarted();
#override
Stream<AuthState> mapEventToState(
AuthEvent event,
) async* {
if (event is VerifyPhone) {
yield* _mapVerifyPhoneToState(event);
}
}
Stream<AuthState> _mapVerifyPhoneToState(VerifyPhone event) async* {
yield AuthStarted();
_auth.verifyPhoneNumber(
phoneNumber: "+" + event.phoneNumber,
timeout: Duration(seconds: 60),
verificationCompleted: (AuthCredential authCredential) {
print("verification completed: auth credential");
},
verificationFailed: (AuthException authException) {
print("verification failed: auth exception");
print(authException.message);
},
codeSent: (String verificationId, [int forceResendingToken]) {
print("code sent verification id" + verificationId);
},
codeAutoRetrievalTimeout: (String verificationId) {
print("auto time" + verificationId);
});
}
}
But i can't use yield inside verifyPhoneNumber callbacks.
The question is how to yield different states inside of callback functions?
You can add an event from your callback. For example, in verificationCompleted, you can do:
verificationCompleted: (AuthCredential authCredential) {
print("verification completed: auth credential");
add(AuthCompleted());
},
And you can handle the AuthCompleted() event on mapEventToState:
#override
Stream<AuthState> mapEventToState(
AuthEvent event,
) async* {
if (event is VerifyPhone) {
yield* _mapVerifyPhoneToState(event);
}
if (event is AuthCompleted){
//Here you can use yield and whathever you want
}
}
PhoneAuthenticationBloc
class PhoneAuthenticationBloc
extends Bloc<PhoneAuthenticationEvent, PhoneAuthenticationState> {
final AuthRepository _authRepository;
final AuthBloc _authBloc;
#override
Stream<PhoneAuthenticationState> mapEventToState(
PhoneAuthenticationEvent event,
) async* {
if (event is PhoneLoadingEvent) {
yield PhoneLoadingState();
} else if (event is PhoneVerificationFailedEvent) {
yield PhoneOTPFailureState(event.failure);
} else if (event is PhoneSmsCodeSentEvent) {
yield PhoneSmsCodeSentState(
verificationId: event.verificationId, resendCode: event.resendId);
} else if (event is PhoneVerifiedOtpEvent) {
yield* _mapToVerifyOtp(event.smsCode, event.verificationId);
}
}
void verifyPhoneNumber(String phoneNumber) async {
try {
add(PhoneLoadingEvent());
await _authRepository.verifyPhoneNumber(phoneNumber,
onRetrieval: (String retrievalCode) {
print("Time Out Retrieval Code: $retrievalCode");
}, onFailed: (Failure f) {
print("OnFailed: ${f.message}");
add(PhoneVerificationFailedEvent(f));
}, onCompleted: (Map<String, dynamic> data) {
print("verificationCompleted: $data");
}, onCodeSent: (String verificationId, int resendCode) {
print("verificationId:$verificationId & resendCode: $resendCode");
add(PhoneSmsCodeSentEvent(
verificationId: verificationId, resendId: resendCode));
});
} catch (e) {
add(PhoneVerificationFailedEvent(Failure(message: e.toString())));
}
}}
UI Screen
builder: (context, state) {
return AppButton(
isLoading: state is PhoneLoadingState,
onPressed: () async {
if (_formKey.currentState.validate()) {
BlocProvider.of<PhoneAuthenticationBloc>(context)
.verifyPhoneNumber(_phoneController.text);
}
},
title: "Continue",
textColor: Colors.white,
);
}