OCDev
OCDev

Reputation: 705

Getting Firebase user token synchronously

I am trying to get a Firebase token to authenticate my calls to a Rest API. I can generate the tokens asynchronously with the following code.

    FirebaseUser mUser = App.getFirebaseAuth().getCurrentUser();
    if (mUser!=null) {
        mUser.getIdToken(false)
                .addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
                    public void onComplete(@NonNull Task<GetTokenResult> task) {
                        if (task.isSuccessful()) {
                            ID_TOKEN = task.getResult().getToken();
                        } else {
                            Log.e(App.TAG, "Firebase Token task ended with error.");
                        }
                    }
                });
    } else {
        Log.i(App.TAG,"User is null, no Firebase Token available");
    }

The ID_TOKEN is a static string variable that holds the result. The issue is, I am constructing my request and adding the authentication headers.

        headers.put("Authentication",
                "Bearer  + ID_TOKEN);

The issue is, since the Firebase token is retrieved asynchronously, sometims the ID_TOKEN variable is empty. I tried forcing the thread to wait for the task using

Tasks.await(task)

But I get an exception saying await cannot be invoked in the main thread.

Is there any other way to get the token synchronously, or make the thread wait until the tasks finishes?

Upvotes: 10

Views: 6302

Answers (6)

Mike Scriven
Mike Scriven

Reputation: 1

You can actually make the main function an async Task instead of void, then you will not get the error when awaiting for the async task to complete

Upvotes: 0

Boy
Boy

Reputation: 7519

I'm doing it like this now:

private suspend fun getTokenResult (firebaseUser: FirebaseUser) = suspendCoroutine<GetTokenResult?> { continuation ->
firebaseUser.getIdToken(true).addOnCompleteListener {
    if (it.isSuccessful) {
        continuation.resume(it.result)
    } else {
        continuation.resume(null)
    }
}

Using a suspended function and the continuation mechanism. So if you are using Coroutines, this might be the easiest way

Upvotes: 7

Amjad Alwareh
Amjad Alwareh

Reputation: 3281

I have done this and It works perfectly. This is the main class:

object FcmToken {
    @JvmStatic
    fun getToken(): String? {
        val task = FirebaseInstanceId.getInstance().instanceId
        try {
            val result = Tasks.await(task)
            return result.token
        } catch (e: ExecutionException) {
            throw IllegalArgumentException("")
        } catch (e: InterruptedException) {
            throw IllegalArgumentException("")
        }
    }
}

In the view model.

viewModelScope.launch(Dispatchers.IO) {
        val result = FcmToken.getToken(viewModelScope)
        Log.e("result", result.toString())
    }

Upvotes: 3

I had the same problem, I needed to update token in Retrofit Authenticator when I got 407. I use CountDownLatch

override fun authenticate(route: Route?, response: Response): Request? {
    val user = FirebaseAuth.getInstance().currentUser
    var token: String? = null
    val lock = CountDownLatch(1)

    user?.getIdToken(true)?.addOnCompleteListener { task ->
        if (task.isSuccessful) {
            token = task.result?.token
            if (token == null) {
                lock.countDown()            //<--unlock 
                return@addOnCompleteListener
            }
            //save token
        } else {
            lock.countDown()                //<--unlock 
            return@addOnCompleteListener
        }
    }
    lock.await()                            //<--wait unlock 

    return if (token != null)
        response.request().newBuilder().header(AUTHORIZATION_KEY, token).build()
    else null
}

Upvotes: 3

Jack
Jack

Reputation: 5614

If possible, just move all your following operations like headers.put(...) into the onComplete block. Which will guarantee the execution order.

Upvotes: 0

Doug Stevenson
Doug Stevenson

Reputation: 317808

It's a really bad idea to force something to run on the main thread synchronously. It could lock up your app and possibly cause it to ANR. That's why Android it complains when you try to run Tasks.await() - it knows there could be a problem.

It's far better to learn how to do do asynchronous programming to do this the right way. You will have to receive the token in the listener that you have to provide.

Please read this blog to learn more about why the Firebase APIs are asynchronous.

Upvotes: -1

Related Questions