tursunov abdulbois
11/17/2021, 2:18 PM@Singleton
class AuthenticationInterceptor @Inject constructor(profileRepository: ProfileRepository, @ApplicationScope coroutineScope: CoroutineScope) :
Interceptor {
private val tokenFlow: Flow<String?> = profileRepository.getProfileToken()
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
val token: String? = runBlocking { // this line should be changed
tokenFlow.firstOrNull()
}
token?.let { requestBuilder.addHeader("Authorization", it) }
return chain.proceed(requestBuilder.build())
}
}
Joffrey
11/17/2021, 2:21 PMusing runBlocking builder in production code is not good practiceI don't believe we can really rule out
runBlocking
generally like this. I would say the main "acceptable" use case for it would be to call coroutines from blocking callbacks that you don't control.
This is exactly your case: intercept()
is a blocking method from the framework you're using, and it needs a result right away as a return value. It doesn't provide any way to give a result later (no future return type or callback function).
So if you need data from a coroutine, intercept
has no choice but to block the calling thread while waiting for this result, so runBlocking
is ok here.StateFlow
's current value directly without suspending:
val token: String? = tokenFlow.value
Note that this suffers from a problem that you already have in your current code: if the coroutine started by stateIn
didn't have time to fetch a non-null value when intercept()
is called the first time, you'll get null.
If this is ok for you, then the above solution with tokenFlow.value
is the way to go.
However, if you actually want to wait for the first non-null value, you can do so using filterNotNull
:
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
val token: String = runBlocking { // runBlocking inevitable if you need to wait
tokenFlow.filterNotNull().first()
}
requestBuilder.addHeader("Authorization", token)
return chain.proceed(requestBuilder.build())
}
tursunov abdulbois
11/17/2021, 2:34 PMThat being said, in your specific case you don't actually need to wait, you could just access the's current value directly without suspending:StateFlow
val token: String? = tokenFlow.valueYou mean in intercept call I will get last emitted token value. Because its flow alaways being collected in application's coroutine scope. Thanks for your feedback
Joffrey
11/17/2021, 2:36 PMfirst()
value from it, which is basically getting the last emitted value (the current "state" of the StateFlow
). And this doesn't require suspensiontursunov abdulbois
11/17/2021, 2:37 PMYes, you're not using the flow as a flow here, you're only getting the first value from it, which is basically getting the last emitted value (the current "state" of theWell noted!) Thanks one more time)). And this doesn't require suspensionStateFlow
gildor
11/17/2021, 3:23 PMtursunov abdulbois
11/17/2021, 3:28 PMNot sure though that you should use StateFlowbfor this caseAny other solution?)
Joffrey
11/17/2021, 3:29 PMgildor
11/17/2021, 3:35 PMJoffrey
11/17/2021, 3:38 PMHowever, if you actually want to wait for the first non-null value, you can do so usingI guess whether this is ok or not depends on how/when the DB is populated.filterNotNull
gildor
11/18/2021, 3:46 AMI guess whether this is ok or not depends on how/when the DB is populated.It’s true, but because context here is some auth token, I just don’t see how it may work by just relaying on database. It’s also common to have case when token is expired, so auth interceptor should handle 401, refresh token and try again without returning 401 to client code
tursunov abdulbois
11/18/2021, 6:24 AMIt’s true, but because context here is some auth token, I just don’t see how it may work by just relaying on database.Token will be updated each time after getting user profile data from GET api call.
It’s also common to have case when token is expired, so auth interceptor should handle 401, refresh token and try again without returning 401 to client codeIt's true. But refreshing token is not yet implemented on server side)
gildor
11/18/2021, 6:54 AMToken will be updated each time after getting user profile data from GET api call.So what will happen when request is started and user profile is not received yet?
tursunov abdulbois
11/18/2021, 7:02 AMSo what will happen when request is started and user profile is not received yet?First token value is obtained after sign in process. Then the first GET user api call will use that token. After its response token will be updated