Reputation: 705
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
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
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
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
Reputation: 154
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
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
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