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.
Related
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)
}
})
}
}
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
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
}
}
My Android Application is base on a TCP protocol.
When I'm initializing a connection to the server, I'm sending a special bytes message and have to wait the response of the server.
In all the repositories example I have seen the repository have always methods to call the source of information with a return (from Android Developers) :
class UserRepository {
private val webservice: Webservice = TODO()
// ...
fun getUser(userId: String): LiveData<User> {
// This isn't an optimal implementation. We'll fix it later.
val data = MutableLiveData<User>()
webservice.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
TODO()
}
})
return data
}
}
The function getUser return a data of LiveData.
In my app the method Login return nothing because I wait for the server to send bytes with a special code to know that is responding to my login request.
Is there a way to implement this pattern with TCP protocols like that ?
Thanks
They honestly should have just written the following code:
class UserRepository {
private val webService: WebService = TODO()
// ...
fun getUser(userId: String, successCallback: (User) -> Unit) {
webService.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
successCallback(response.body())
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
}
})
}
}
LiveData is not meant to represent one-off callbacks.
Then call it as
userRepository.getUser(userId) { user ->
// do whatever
}
For a proper reactive implementation, refer to https://stackoverflow.com/a/59109512/2413303
I am currently developing an Android application that uses the Retrofit library for REST api usage.
For instance, I have the following code from MainActivity.kt :
fun userLogin(){
calls.userLogin() { updateUiComponents() }
}
fun updateUiComponents(){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}
And I have in a separate file the definition of the Retrofit calls:
fun userLogin(postActionMethod: () -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod()
}
})
}
After the Retrofit call is implemented and it is successful, reaching the onResponse method, I would like to send the Response object as a parameter of the lambda function back to the MainAcativity.kt. From the MainActivity.kt, the lambda function would use this information to perform some specific task.
Is that a way of defining a lambda function like this, with arguments? If it is the case, how can I pass the lambda function as a parameter like done on the following line:
calls.userLogin(body) { updateUiComponents() }
Thank u!
I don't know if I get what your problem is but a lambda does not need to do not have any parameter. You can easily do something like
fun userLogin(postActionMethod: (Response<LoginResponse>?) -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod(response)
}
})
}
so you consume it with
fun userLogin(){
calls.userLogin() { updateUiComponents(it) }
}
fun updateUiComponents(response: Response<LoginResponse>?){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}