Async function is not returning expected data in Flutter

Viewed 701

I am trying to verify phone number using firebase. But getting unexpected result from async function. Here is my code :

  bool isVerificationSuccess = false;

  Future<bool> verifyUserPhoneNumber(String phoneNumber) async {
    await FirebaseAuth.instance.verifyPhoneNumber(
      phoneNumber: phoneNumber,
      timeout: Duration(seconds: 60),
      verificationCompleted: (credential) => verificationComplete(credential),
      verificationFailed: (authException) => verificationFailed(authException),
      codeAutoRetrievalTimeout: (verificationId) =>
          codeAutoRetrievalTimeout(verificationId),
      codeSent: (verificationId, [code]) => smsCodeSent(verificationId, [code]),
    );
    print("Status from service : $isVerificationSuccess");
    return isVerificationSuccess;
  }

  verificationComplete(AuthCredential credential) async {
    FirebaseUser user = await FirebaseAuth.instance.currentUser();

    user.linkWithCredential(credential).then((_) {
      print("User Successfully Linked");
      isVerificationSuccess = true;
    }).catchError((e) {
      print("Linking Error : ${e.toString()}");
    });
  }

Here is the output :

 Status from service : false
 User Successfully Linked

So here verifyUserPhoneNumber function returns even before the completion of FirebaseAuth.instance.verifyPhoneNumber(), so its not returning expected data (always false) even when the verification is successful. Whats wrong here?

3 Answers

The issue is that the function call in this line:

verificationCompleted: (credential) => verificationComplete(credential),

is not awaited, even though the function itself is asynchronous. So what happens is:

  1. verifyUserPhoneNumber is called from somewhere
  2. FirebaseAuth.instance.verifyPhoneNumber is awaited
  3. Within this call, verificationComplete(credential) is called and, because it itself contains an await statement, it will be queued up by the Dart event loop. So whatever happens after FirebaseUser user = await FirebaseAuth.instance.currentUser(); is delayed!
  4. verifyUserPhoneNumber now continues executing and returns false.
  5. After a value has already been returned, it is now verificationComplete's turn to process. It will change the boolean value, and then exit.

My recommendation here is not to use a global variable, but instead, for example, call verificationComplete after FirebaseAuth.instance.verifyPhoneNumber has already fully finished processing and returned.

Edit: This is how you could theoretically fix the issue, though it is not particularly pretty I do admit:

  Future<bool> verifyUserPhoneNumber(String phoneNumber) async {
    final completer = Completer<AuthCredential>();
    await FirebaseAuth.instance.verifyPhoneNumber(
      phoneNumber: phoneNumber,
      timeout: Duration(seconds: 60),
      verificationCompleted: completer.complete,
      verificationFailed: completer.completeError,
      codeAutoRetrievalTimeout: (verificationId) =>
          codeAutoRetrievalTimeout(verificationId),
      codeSent: (verificationId, [code]) => smsCodeSent(verificationId, [code]),
    );
    try {
      final credential = await completer.future;
      return await verificationComplete(credential);
    } catch (e) {
      verificationFailed(e);
      return false;
    }
  }

  Future<bool> verificationComplete(AuthCredential credential) async {
    FirebaseUser user = await FirebaseAuth.instance.currentUser();
    try {
      await user.linkWithCredential(credential);
      print("User Successfully Linked");
      return true;
    } catch (e) {
      print("Linking Error : ${e.toString()}");
      return false;
    }
  }

if u can place in isVerificationSuccess = true; inside function verificationComplete(credential) then u might get true value

So here verifyUserPhoneNumber function returns even before the completion of FirebaseAuth.instance.verifyPhoneNumber(), so its not returning expected data (always false) even when the verification is successful. Whats wrong here?

FirebaseAuth.instance.verifyPhoneNumber uses a method channel plugins.flutter.io/firebase_auth and calls a native method for authentication.

It uses the call back specified in the argument verificationCompleted once verification completes. However, from the function definitions, it seems that FirebaseAuth.instance.verifyPhoneNumber expects a type void Function(AuthCredential). However, you provide Future<void> Function(AuthCredential)

Whats wrong here?

It seems that what is wrong is to expect that FirebaseAuth.instance.verifyPhoneNumber will wait for an async call back Future<void> Function(AuthCredential) to complete when it expects a void Function (AuthCredential).

So it just calls your verificationComplete(AuthCredential credential) async implementation which returns a Future<void>. At this moment, FirebaseAuth.instance.verifyPhoneNumber is complete and the control moves ahead to print and return isVerificationSuccess which is still false as the future returned by verificationComplete(AuthCredential credential) async is not resolved yet.

So,

  1. Return a Future<void> from verifyUserPhoneNumber
  2. Update the App's state using a setState inside verificationComplete after you assign isVerificationSuccess = true; to rebuild your app after a successful verification
Related