I’m working with Datastore and it’s asynchronous n...
# android
s
I’m working with Datastore and it’s asynchronous nature and Okhttp authentications. Having some trouble breaching this Java <-> Coroutines gap. More details in thread 🧵
Basically inside an okhttp Interceptor, I need to access the saved bearer token to attach to the backend request, however as Interceptors do not provide suspend functions, I am left with some weird options. Either I simply do something like this inside the intercept function, which I am fairly certain is not the best idea to block like this
Copy code
override fun intercept(chain: Interceptor.Chain): Response {
    val currentToken = runBlocking {
        sessionManager.getCurrentToken()
    }
    return chain.proceed(
        chain.request().withAuthenticationToken(currentToken)
    )
}
Or I try and do something different and go with an approach that might look like this
Copy code
class ExistingTokenAppendingInterceptor(
    sessionManager: SessionManager,
) : Interceptor {
    private var currentToken: String? = null

    init {
        sessionManager.getTokenFlow()
            .onEach { token ->
                currentToken = token
            }
            .launchIn(GlobalScope) // TODO Pass appropriate scope? Handle this differently?
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        return chain.proceed(
            currentToken?.let { token ->
                chain.request().withAuthenticationToken(token)
            } ?: chain.request()
        )
    }
}
Which also doesn’t look that nice since I am observing the Datastore on the GlobalScope, even the linter doesn’t like that. Maybe this solution would look nice if I provide some sort of application-wide coroutine scope instead to launch the flow listening on it? Not sure how I would do that either. Any ideas would be appreciated.
l
Splitties Preferences has an implementation of SharedPreferences that sits on top of AndroidX DataStore. You can take inspiration from it, or even use it directly if it works for you. Anyway, the solution is to make the storage be just the storage, where you save to it asynchronously, while keeping track of the latest value separately, for example in a MutableSharedFlow or a MutableStateFlow.
o
In case of the interceptor the
intercept
method is called on the worker thread. So as for me it is a valid example to use
runBlocking
here. It is even in it's doc:
It is designed to bridge regular blocking code to libraries that are written in suspending style
s
Louis, thank you for the input, I will have to admit I didn’t really get what you were saying there. How would it realistically look like to access those flows from inside the interceptor anyway? I would still need them to be scoped somewhere where they will stay alive for the entirety of my Application lifecycle right?
Oleksandr, yeah it’s good that it’s ran on a worker thread, so far it’s been working fine for me. I’ve just seen many examples all over the place as to why runBlocking is not a good idea, but it seems like it’s at least somewhat appropriate for this use case. I will keep it this way for now probably.
l
Both StateFlow and SharedFlow allow access to the latest value, and that's what you'd get in the interceptor.
s
Right sorry, this is the easy part, could do something like this
Copy code
val tokenState: StateFlow<String?> = getTokenFlow()
    .stateIn(
        scope = ??, // Note here
        started = SharingStarted.Eagerly,
        initialValue = null
    )

override fun intercept(chain: Interceptor.Chain): Response {
    val currentToken = sessionManager.tokenState.value
    return if (currentToken == null) {
    // etc.
The part I am struggling with is what should that StateFlow be scoped to. All I see on the official documentations are about viewmodel scopes etc, but in this case I would need a global one, but
GlobalScope
can’t be it right?
l
Make a custom scope that you name
AppScope
or alike.