I am building client:
OkHttpClient().newBuilder()
.authenticator(object : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val request = AppPreferences.refreshToken?.let {
api.refreshTokenC(it).execute()
}
...
})
and for this I use usual fun which returns Call :
#POST("Accounts/refresh")
fun refreshTokenC(#Query("refreshToken") refreshToken: String): Call<TokenResponse>
But how can use it with suspend function?:
#POST("Accounts/refresh")
suspend fun refreshToken(#Query("refreshToken") refreshToken: String): Response<TokenResponse>
fun authenticate(route: Route?, response: Response) is not suspend function, we cannot invoke suspend function from it.
Can use runBlocking:
Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. 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.
fun authenticate(route: Route?, response: Response) is being called on a background thread, we can use runBlocking safely.
val request = AppPreferences.refreshToken?.let { token ->
runBlocking { api.refreshToken(token) }
}
Similar code: AuthInterceptor.kt
Related
I have a working application, but I would like to improve one point. The method ("private fun save"), which is responsible for saving the information I need, I would like to make asynchronous.
But the problem is that when I change it to - "private suspend fun save", I have to make suspend and override fun intercept method. But since it is override, I get an error:
Conflicting overloads: public open suspend fun intercept(chain:
Interceptor.Chain): Response defined in
com.pocketscout.network.PocketScoutInterceptor, public abstract fun
intercept(chain: Interceptor.Chain) : Response defined in
okhttp3.Interceptor.
Is this problem somehow solved?
class PocketScoutInterceptor(
private val appContainer: PocketScoutContainer,
) : okhttp3.Interceptor {
#Throws(IOException::class)
override fun intercept(chain: okhttp3.Interceptor.Chain): okhttp3.Response {
val packet = buildPacket(timestamp, duration, request, response, description)
save(packet)
return response ?: okhttp3.Response.Builder().build()
}
At this moment OkHttp3 doesn't support suspend feature for interceptors. You can wrap save method
private suspend fun save(packet: Packet) { // Make suspend
...
}
runBlocking { save(packet) } // Call inside intercept
or all inside it:
private fun save(packet: Packet) = runBlocking {
...
}
Use this code at your own risk.
Recently, I applied Coroutines into my project everything seems to be fine but today I meet a problem [Upload file/image into server using Coroutine + Retrofit][1]
It seems that there is no solution for upload file using coroutine + Retrofit, so we must use callback for retrofit.
//Api interface
interface UploadFileApiKotlin {
#Multipart
#POST("/uploadFileApi")
fun uploadFiles(
#Part listUri: List<MultipartBody.Part>
): Call<Response<List<FileResponse>?>>
}
//ViewModel class
serviceScope.launch {
//Insert into db
repository.insertNewExtAct()
//Send data into server.
val call = RequestHelper.getUpLoadFilesKotlinRequest().uploadFiles(partList)
call.enqueue(object : Callback<Response<List<FileResponse>?>> {
override fun onResponse(
call: Call<Response<List<FileResponse>?>>,
response: Response<Response<List<FileResponse>?>>
) {
TODO("Not yet implemented")
}
override fun onFailure(
call: Call<Response<List<FileResponse>?>>,
t: Throwable
) {
TODO("Not yet implemented")
}
})
//Wait for response from server and do logic
}
My question is: How can I suspend coroutines's execution to wait for retrofit's callback ?
Thank you bro.
[1]: Upload file/image into server using Coroutine + Retrofit
You can use suspendCoroutine or suspendCancellableCoroutine to work with callbacks (to convert a callback to a suspend function):
serviceScope.launch {
//Insert into db
repository.insertNewExtAct()
//Send data into server.
uploadFile()
//Wait for response from server and do logic
}
suspend fun uploadFile() = suspendCoroutine<Response<List<FileResponse>?>> { continuation ->
val call = RequestHelper.getUpLoadFilesKotlinRequest().uploadFiles(partList)
call.enqueue(object : Callback<Response<List<FileResponse>?>> {
override fun onResponse(
call: Call<Response<List<FileResponse>?>>,
response: Response<Response<List<FileResponse>?>>
) {
// Resume coroutine with a value provided by the callback
continuation.resumeWith(response.data)
}
override fun onFailure(
call: Call<Response<List<FileResponse>?>>,
t: Throwable
) {
continuation.resumeWithException(t)
}
})
}
suspendCoroutine suspends coroutine in which it executed until we decide to continue by calling appropriate methods - Continuation.resume.... suspendCoroutine mainly used when we have some legacy code with callbacks.
There is also suspendCancellableCoroutine builder function, it behaves similar to suspendCoroutine with additional feature - provides an implementation of CancellableContinuation to the block.
I am reading about Kotlin coroutine in Google 's documentation. I'm adviced to use withContext(Dispacher.IO) to a different thread to main-safety. But I have a problem , fetchData() done before response from server so fetchData() return null result. Any help that I appreciate.
https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe
class GameRemoteDataSource #Inject constructor(val api : GameApi) {
val IODispatcher: CoroutineDispatcher = Dispatchers.IO
suspend fun fetchData() : Resource<ListGameResponse> {
var resource : Resource<ListGameResponse> = Resource.loading(null)
withContext(IODispatcher){
Log.d("AAA Thread 1", "${Thread.currentThread().name}")
api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse>{
override fun onResponse(
call: Call<ListGameResponse>,
response: Response<ListGameResponse>
) {
if(response.code()==200){
resource = Resource.success(response.body())
}else{
resource = Resource.success(response.body())
}
Log.d("AAA code",response.code().toString())
}
override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
resource = Resource.error(t.message.toString(),null)
Log.d("AAA Thread", "${Thread.currentThread()}")
}
})
Log.d("AAA Thread", "${Thread.currentThread()}")
Log.d("AAA resource",resource.data.toString()+ resource.status.toString())
}
return resource
}
}
withContext is not helpful for converting an asynchronous function with callback into suspending code that can be used in a coroutine. It is more applicable to converting synchronous blocking code. Your non-working strategy of creating an empty variable and trying to fill it in the callback to synchronously return is described in the answers to this question.
For an asynchronous function with callback, if it returns a single value like your code above, this is typically converted to a suspend function using suspendCoroutine or suspendCancellableCoroutine. If it returns a series of values over time (calls the callback multiple times), it would be fitting to use callbackFlow to convert it to a Flow that can be collected in a coroutine.
But it looks like you're using Retrofit, which already has a suspend function alternatives to enqueue so you don't need to worry about all this. You can use the await() or awaitResponse() functions instead. In this case, await() would return ListGameResponse and awaitResponse() would return Response<ListGameResponse>. So awaitResponse() is better if you need to check the response code.
Awaiting returns the response and throws an exception if there's an error, so you can use try/catch instead of adding a failure listener.
class GameRemoteDataSource #Inject constructor(val api : GameApi) {
suspend fun fetchData(): Resource<ListGameResponse> {
return try {
val response = api.getAllGame(page = 1).awaitResponse()
Log.d("AAA code", response.code().toString())
Resource.success(response.body())
} catch (exception: Exception) {
Resource.error(exception.message.toString(),null)
}
}
}
You should use suspendCancellableCoroutine to convert asynchronous API into a coroutine flow, like this
suspend fun fetchData(): ListGameResponse = withTimeout(Duration.seconds(60)) {
suspendCancellableCoroutine<ListGameResponse> { cont ->
api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse> {
override fun onResponse(
call: Call<ListGameResponse>,
response: Response<ListGameResponse>
) {
Log.d("AAA code", response.code().toString())
cont.resume(response.body())
}
override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
}
Here is what I already know:
Retrofit has the enqueue function and the execute function. The enqueue function executes on a different (background) thread and then returns the response using the callback. The execute function executes on the calling Thread and returns the response directly. enqueue can be called on the UI Thread while execute shouldn't be called on the UI Thread.
But I am now wondering, which of the following two options is better.
Call enqueue in a normal function:
fun makeRequest() {
getCall().enqueue(
object : Callback<ResponseBody> {
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
if (response.isSuccessful) {
//unsuccessful
}
//successful
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
//failed
}
}
)
}
or call execute in a suspend function on a background thread:
suspend fun makeRequest() = withContext(Dispatchers.IO) {
val call = getCall()
try {
val response = call.execute()
if (!response.isSuccessful) {
//unsuccessful
}
//successful
} catch (t: Throwable) {
//failed
}
}
Which one of these is preferable?
Coroutines have cleaner syntax, so that's a plus. And if you're familiar with coroutine SupervisorJob, you can more easily cancel groups of requests. Other than that, they are largely the same except for which background thread is getting used for the request. But Retrofit already has built-in coroutine support, so your second version can be cleaner than what you have:
suspend fun makeRequest() { // Can be called from any dispatcher
try {
val response = getCall().awaitResponse()
if (!response.isSuccessful) {
//unsuccessful
}
//successful
} catch (t: Throwable) {
//failed
}
}
I want to call blocking a suspend function in a normal function, but does not block Thread to finishing suspend function and then return Response
override fun intercept(chain: Interceptor.Chain): Response {
// getSession is a suspend function
val session = sessionProvider.getSession()
return chain.proceed(
chain
.request()
.newBuilder()
.addHeader("Authorization", "${session.token}")
.build()
)
}
This looks like you are implementing an OkHttp interceptor, so I am hoping that intercept() is being called on a background thread.
If so, use runBlocking():
override fun intercept(chain: Interceptor.Chain): Response {
// getSession is a suspend function
val session = runBlocking { sessionProvider.getSession() }
return chain.proceed(
chain
.request()
.newBuilder()
.addHeader("Authorization", "${session.token}")
.build()
)
}
runBlocking() will execute the suspend function, blocking the current thread until that work is complete.