Hi, guys. I have AuthtenticationInterceptor to add...
# coroutines
t
Hi, guys. I have AuthtenticationInterceptor to add token into header of request. In overrided intercept function it uses runBlocking builder to get token (token is obtained as flow object. Its repository implements PreferencesDataStore). I should replace runBlocking builder to other proper implementation. Because using runBlocking builder in production code is not good practice. Any sugestions?
Copy code
@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())
    }
}
j
using runBlocking builder in production code is not good practice
I 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.
5
That being said, in your specific case you don't actually need to wait, you could just access the
StateFlow
's current value directly without suspending:
Copy code
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
:
Copy code
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())
    }
t
That being said, in your specific case you don't actually need to wait, you could just access the 
StateFlow
 's current value directly without suspending:
val token: String? = tokenFlow.value
You 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
👌 1
j
Yes, 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 the
StateFlow
). And this doesn't require suspension
t
Yes, 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 the 
StateFlow
). And this doesn't require suspension
Well noted!) Thanks one more time)
g
runBlocking is absolutely fine in this case
Not sure though that you should use StateFlowbfor this case
t
Not sure though that you should use StateFlowbfor this case
Any other solution?)
j
Looks ok to me, if the goal is to keep this property in sync with the DB so the interceptor has fast access to the latest value
1
g
In theory yes, but you don't want to be in situation when requests which require auth just run and fail when you don't have token in storage, instead you want to wait (block interceptor) until token is received, for example first request with auth should trigger authorization to populate this database.
j
I see, that was kind of what I was mentioning above:
However, if you actually want to wait for the first non-null value, you can do so using 
filterNotNull
I guess whether this is ok or not depends on how/when the DB is populated.
g
I 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
t
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.
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 code
It's true. But refreshing token is not yet implemented on server side)
@gildor thanks for your responses)
g
Token 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?
t
So 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