FarBen
FarBen

Reputation: 95

Use Firebase Phone Authentication with Coroutines

I want to create a signing activity in my app using Firebase phone authentication. The authentication has three phases:

  1. Sending verification code with PhoneAuthProvider.verifyPhoneNumber(options)
  2. Verify the code with PhoneAuthProvider.getCredential(verificationId!!, code)
  3. Sign in the user with auth.signInWithCredential(credential)

I want to use Coroutines to handle the signing process. I know how to handle code verification using await(), which wrap the Task returned by auth.signInWithCredential(credential).

My problem is with PhoneAuthProvider.verifyPhoneNumber(options) function which is a void function. I must use callbacks to handle this method.

val options = PhoneAuthOptions.newBuilder(auth)
      .setPhoneNumber(phoneNumber)
      .setTimeout(60L, TimeUnit.SECONDS)
      .setCallbacks(callback)
      .build()
PhoneAuthProvider.verifyPhoneNumber(options)

where callbacks is:

callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks(){
            override fun onVerificationCompleted(credential: PhoneAuthCredential) {
                Timber.i("onVerificationCompleted:$credential")
                signInWithPhoneAuthCredential(credential)
            }

            override fun onVerificationFailed(e: FirebaseException) {
                Timber.i(e,"onVerificationFailed")
            }

            override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) {
                Timber.i("onCodeSent:$verificationId")
                storedVerificationId = verificationId
                resendToken = token
            }
        }

The question is: is there a way to use await() with verifyPhoneNumber function? Otherwise, how can I use coroutines with callbacks to block the function until callbacks triggered?

Upvotes: 0

Views: 908

Answers (2)

Javid Sattar
Javid Sattar

Reputation: 846

You can use the below solution for this problem

First of all, you must define an interface FcmService

interface FcmService {
     suspend fun initFcmToken() : Flow<String>
}

for next section define FcmServiceImpl

class FcmServiceImpl : FcmService {
override suspend fun initFcmToken(): Flow<String> =
    callbackFlow {
       FirebaseMessaging.getInstance().token.addOnCompleteListener(
            OnCompleteListener { task ->
                if (!task.isSuccessful) {
                    Logger.error(
                        "Device Manager" +
                                "Fetching FCM registration token failed" +
                                task.exception
                    )
                    return@OnCompleteListener
                }
                if (task.isComplete) {
                    trySend(task.result)
                }
            })
        awaitClose { cancel() }
    }
 }

and then for use

fcmService.initFcmToken().collectLatest {

//  your token is here

}

I hope help

Upvotes: 0

You can use suspendCoroutine to wrap Firebase callback into a coroutine suspend function like this:

sealed class PhoneAuthResult {
    data class VerificationCompleted(val credentials: PhoneAuthCredential) : PhoneAuthResult()
    data class CodeSent(val verificationId: String, val token: PhoneAuthProvider.ForceResendingToken)
        : PhoneAuthResult()
}

private suspend fun performPhoneAuth(
    phoneNumber: String,
    firebaseAuth: FirebaseAuth): PhoneAuthResult = 
    suspendCoroutine { cont ->
        val callback = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
            override fun onVerificationCompleted(credential: PhoneAuthCredential) {
                Timber.i("onVerificationCompleted:$credential")
                cont.resume(
                    PhoneAuthResult.VerificationCompleted(credential)
                )
            }

            override fun onVerificationFailed(e: FirebaseException) {
                Timber.i(e, "onVerificationFailed")
                cont.resumeWithException(e)
            }

            override fun onCodeSent(verificationId: String, token: PhoneAuthProvider.ForceResendingToken) {
                Timber.i("onCodeSent:$verificationId")
                cont.resume(
                    PhoneAuthResult.CodeSent(verificationId, token)
                )
            }
        }
        
        val options = PhoneAuthOptions.newBuilder(firebaseAuth)
            .setPhoneNumber(phoneNumber)
            .setTimeout(60L, TimeUnit.SECONDS)
            .setCallbacks(callback)
            .build()

        PhoneAuthProvider.verifyPhoneNumber(options)
    }

Upvotes: 6

Related Questions