Viewed
Viewed

Reputation: 1333

How to wait result of suspend function?

I need to add freshCsrf string to each request into form body. Uses OkHttpClient with AuthInterceptor. The problem is how to send request inside interceptor in order to get the freshCsrf string?

In code below occurs error

Suspend function 'getAuth' should be called only from a coroutine or another suspend function
class AuthInterceptor(val authApi: IAuthService) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()

        val freshCsrf = authApi.getAuth().freshCsrf

        // ... add freshCsrf to form body
        return chain.proceed(request)
    }
}

// Retrofit service
interface IAuthApi {
    @GET("/auth")
    suspend fun getAuth(): Auth
}

I try to use coroutine, but also failed because can't wait result. One of example with async/await case ended with error

Suspend function 'await' should be called only from a coroutine or another suspend function
    private val scope = CoroutineScope(Dispatchers.IO)
    
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        val freshCsrf = scope.async { authApi.getAuth().freshCsrf }
        freshCsrf.await()
        
        // ... add freshCsrf to form body

        return chain.proceed(request)
    }

Update 1

I confused with runBlocking description

It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.

In one side, retrofit interface is suspending and I need to wait network result in order to continue creating other request (bridge - ok). But in the other side, it isn't main function or test. And many tutorials and articles tell that must to avoid runBlocking in production code. Can you explain it?

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        val freshCsrf = runBlocking {
            return@runBlocking authApi.getAuth().freshCsrf
        }
        
        // ... add freshCsrf to form body

        return chain.proceed(request)
    }

Upvotes: 2

Views: 5911

Answers (3)

Brijesh Chavda
Brijesh Chavda

Reputation: 39

interface ApiInterface {
@GET("user/list")
fun userListGet(): Call<UserPageResponse>
}


lifecycleScope.launch {
val response = apiInterface.userListGet().await()
}

Upvotes: 0

Łukasz Gawron
Łukasz Gawron

Reputation: 51

If you don't want to use runBlocking then you will have to introduce second endpoint that returns Call<Auth> in the IAuthApi interface. Call<Auth> will allow you to run execute() function on it to run a request in synchronous (blocking) way.

Call interface documentation

Below how it can look like using Call.execute:

class AuthInterceptor(val authApi: IAuthService) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()

        val freshCrsf = authApi.getAuthCall().execute().let { authResponse ->
            if(authResponse.isSuccessful) {
                authResponse.body().freshCrsf
            } else {
                // TODO handle error for example you can read authResponse.errorBody()
                // and do something based on it
            }
        }

        // ... add freshCsrf to form body
        return chain.proceed(request)
    }
}

// Retrofit service
interface IAuthApi {
    @GET("/auth")
    fun getAuthCall(): Call<Auth>

    @GET("/auth")
    suspend fun getAuth(): Auth
}

I think runBlocking in this case is not a bad option really. As documentation state it's used to bridge suspending code to the blocking one. Main function and tests for me are common use cases.

Upvotes: 5

Martin Zeitler
Martin Zeitler

Reputation: 76807

Use .execute() instead of .enqueue() and it will perform the request synchronously.
Interceptor is not required, as CSRF protection usually resides in the HMTL <head>;
this would be something else, if the string would be served in the HTTP headers.

The relevent documentation: Interface Call<T>.

Upvotes: 4

Related Questions