Giter VIP home page Giter VIP logo

retrofit2-kotlin-coroutines-adapter's Introduction

Kotlin Coroutine Adapter (DEPRECATED)

A Retrofit 2 CallAdapter.Factory for Kotlin coroutine's Deferred.

This library is deprecated. Please migrate to Retrofit 2.6.0 or newer and its built-in suspend support

Usage

Add CoroutineCallAdapterFactory as a Call adapter when building your Retrofit instance:

val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .build()

Your service methods can now use Deferred as their return type.

interface MyService {
  @GET("/user")
  fun getUser(): Deferred<User>

  // or

  @GET("/user")
  fun getUser(): Deferred<Response<User>>
}

Download

If you are using Kotlin 1.3, download the latest JAR or grab via Maven:

<dependency>
  <groupId>com.jakewharton.retrofit</groupId>
  <artifactId>retrofit2-kotlin-coroutines-adapter</artifactId>
  <version>0.9.2</version>
</dependency>

or Gradle:

implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'

If you are using Kotlin pre-1.3 and experimental coroutines, download its latest JAR or grab via Maven:

<dependency>
  <groupId>com.jakewharton.retrofit</groupId>
  <artifactId>retrofit2-kotlin-coroutines-experimental-adapter</artifactId>
  <version>1.0.0</version>
</dependency>

or Gradle:

implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0'

Snapshots of the development version are available in Sonatype's snapshots repository.

License

Copyright 2017 Jake Wharton

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

retrofit2-kotlin-coroutines-adapter's People

Contributors

jakewharton avatar kemoke avatar mlegy avatar ryansname avatar saeednt avatar takahirom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

retrofit2-kotlin-coroutines-adapter's Issues

Fire and forget

I have a use case where I'm hitting a server, but I do not need the response at all. Is there a way to use coroutines to support this?

Thread is different first and next

here is my code

class HelloRepository(api: HelloApi) {
    suspend fun getHello() {
        Log.e("start", "thread id = ${Thread.currentThread().id}")
        val hello = api.getHello().await()
        Log.e("end", "thread id = ${Thread.currentThread().id}")
    }
}

class HelloUseCase(repository: HelloRepository) {
    fun execute(onComplete:(Hello) -> Unit) : Job {
        job = Job()
        CoroutineScope(Dispatchers.Main + job).launch {
            onComplete(repository.getHello())
        }
        return job
    }
}

class ViewModel {
    // execute on activity create
    fun getHello() {
        jobs += helloUseCase.execute(...)
    }
}

// And then cancel the job on actvitiy destroy
class Activity {
    onDestoy() {
        jobs.forEach {
            it.cancelChildren()
            it.cancel()
        }
    }
}

the log App on launch
start thread id = 123
end thread id = 123

the log app on re-launch(press back key and click app shortcut)
start thread id = 123
end thread id = 126

Why the Thread is different first launch and then next launch?

Cancel Coroutine would cancel the call?

 CoroutineScope(Dispatchers.IO+job).launch {
            request = App.redditApi?.getTop("after", "en")
            withContext(Dispatchers.Main) {
                try {
                    val response = request?.await()
                    if (response!!.isSuccessful) {
                        val news: News? = response?.body()
              
                        //Do something with response e.g show to the UI.
                    } else {
                  
                    }
                } catch (e: HttpException) {
                } catch (e: Throwable) {
                    e.printStackTrace()
                }
            }
        }
        button.setOnClickListener { _: View? ->
         // request?.cancel()
            job?.cancel()
}

I want to ask , I called the url to server side. And then called cancel.Would the work on server side be cancelled too by cancelling the job? Because thats what I want. OR should I uncomment request?.cancel() part.

ClassCastException: kotlin.Result$Failure cannot be cast to retrofit2.Response

Is there a way to handle exceptions from Retrofit? We often get these ClassCastExceptions in the await() call.

interface Api {
  @GET("api/config")
  fun getConfigAsync(): Deferred<Response<String>>
}
launch { // UI
  try {
    val response = api.getConfigAsync().await()
    doSomething(response)
  } catch (e: Exception) {
    Timber.e(e)
  }
}
Exception: java.lang.ClassCastException: kotlin.Result$Failure cannot be cast to retrofit2.Response
  at com.example.Setup$loadConfig$1.invokeSuspend(Setup.kt:385)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
  at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:285)
  at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
  at kotlinx.coroutines.scheduling.CoroutineScheduler.access$getSchedulerName$p(CoroutineScheduler.kt:60)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

okhttp interceptor throw SocketTimeoutException caused the crash

I use retrofit2 and interface return Deferred object, then I got an SocketTimeoutException using the okhttp interceptor, the whole app crashed.

Before using the coroutine, this SocketTimeoutException will not cause app crash, so I don't know how to deal with this problem.

interface api {
@get("info")
fun request(): Deferred
}

class RequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response? {
var request = chain.request()
// do something
return chain.proceed(request)
}
}

when I disconnect the network to request and get a retrofit2.httpexception, the app also crashed.

g_82kn4cs1e0o6ml9koqj60

I have used the safeApiCall function to catch exceptions per network request.

suspend fun safeApiCall(call: suspend () -> Response): Response {
return try {
call()
} catch (e: Exception) {
Response.error(IOException(e))
}
}

Null properties in the returned object

For any objects with no default value (such as int, string, etc), the properties will be null when they could not be obtained from the api's Json response. This is the case even when the properties are NOT nullable. NPE exception is thrown when trying to use them. My retrofit module is the following:

@Module
internal class TestRetrofitModule {

    @Provides
    @RepositoryScope
    fun retrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit =
            Retrofit.Builder()
                    .client(okHttpClient)
                    .baseUrl("http://localhost:$TEST_API_PORT/api/")
                    .addCallAdapterFactory(CoroutineCallAdapterFactory())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build()

    @Provides
    @RepositoryScope
    fun gson(): Gson = GsonBuilder().create()

    @Provides
    @RepositoryScope
    fun okHttpClient(): OkHttpClient = OkHttpClient.Builder()
            .build()

    companion object {
        const val TEST_API_PORT = 54055
    }
}

Also, relevant gradle dependencies:

implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0'

Correct me if I'm wrong but my expectation would be that I get some sort of parsing exception instead of an object with null values for non-null properties.

[Question] Coroutine cancellation is not handled, right?

The coroutineContext is not shared with the calling coroutine, right?
Did anyone find a way to wrap these calls so cancel() is called on the returned Deferred instance if the calling coroutine is cancelled?

Also, @JakeWharton, I remember you talking about an idea on how to directly support suspend functions in Retrofit in a Tweet. Would it handle cancellation and cancel any in progress request?

Compiled by a pre-release version

Trying to use 1.3.0-rc-57, but I can't with the adapter. I am receiving the following error: Class 'com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory' is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler

Is it possible to know the called method when an error occurred?

When an error occurs on Deferred.await() the thrown error stacktrace is not showing any indication of the url/method that was actually called:

Exception in thread "main" retrofit2.HttpException: HTTP 404 Not Found
	at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
	at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
	at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
	at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

The code that produces it is this:

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import kotlinx.coroutines.Deferred
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import retrofit2.Retrofit
import retrofit2.http.GET


suspend fun main() {
    createService("http://www.google.com/", MyService::class.java)
        .run()
        .await()
}

fun <T> createService(baseUrl: String, clazz: Class<T>): T {
    val httpClient = OkHttpClient.Builder().build()
    val retrofit = Retrofit.Builder().baseUrl(baseUrl)
        //for coroutines deferred
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .client(httpClient).build()
    return retrofit.create(clazz)
}

interface MyService {

    @GET("blabla")
    fun run(): Deferred<ResponseBody>
}

I am thinking here of a couple of alternatives:

Any other suggestion?

Is there any way to catch timeout exception using your coroutines?

I configured Retrofit like this:

object RetrofitFactory {
    fun makeRetrofitService(): RetrofitDownloadAPIService {
        val okHttpClient = OkHttpClient().newBuilder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .build()
        return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .client(okHttpClient)
                .build().create(RetrofitDownloadAPIService::class.java)
    }
}

and then tried to catch SocketTimeoutException like this

try {
            val request = retrofitBuildService.service.downloadPDF(codeType, uuid)
            val response = request.await()
            if (response.isSuccessful) {
                if (response.headers().get("Content-Type").equals(PDF_CONTENT_TYPE)) {
                    return writeToFile(response.body()!!)
                } else {
                    val responseText = response.body()!!.string()
                    JSONParser.jsonErrorParser(responseText)

                }
            } else
                throw ResponseException(response.code())
        }catch (ste:SocketTimeoutException){
            throw NetworkException(ste)
        }

but nothing happened
May be I did something wrong, or there are not way to do timeout?
Thank you

Coroutines 1.0.0

Do you have any plan to update for Coroutines 1.0.0 removing experimental?

Gradle and Java 6?

Why does the gradle script have java source at
targetCompatibility = JavaVersion.VERSION_1_6
sourceCompatibility = JavaVersion.VERSION_1_6
Shouldn't we be using 8 now?

[Question] Retrieving Error response body

When a network error occurs, something like a HTTP 400 error. Is there any way to get access to the error response body ? I need access to the custom error msg being sent in the error body.

Why do you use call.enqueue() instead of call.execute() in CallAdapter?

@JakeWharton As I understand call.enqueue() dispatches request calling to retrofit internal mechanism (it works async and we get callbacks). But it is a coroutines-adapter and coroutines are an async framework itself. It looks like one async framework use another one. Can we just use call.execute() and return Deferred (but of course keep in mind to launch it in BG dispatcher)?

Also there is a problem to handle errors for example occurred in Interceptors. And we cannot catch those within coroutines. It just crash the application.

2018-12-19 17:56:17.501 29846-29916/com.sample.app E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher Process: com.sample.app, PID: 29846 java.lang.Error: java.lang.Exception: Token exception at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1173) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:764) Caused by: java.lang.Exception: Token exception at com.sample.app.interceptors.TokenInterceptor.intercept(TokenInterceptor.kt:45) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) at com.sample.app.interceptors.SentryInterceptor.intercept(SentryInterceptor.kt:17) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:254) at okhttp3.RealCall$AsyncCall.execute(RealCall.java:200) at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)  at java.lang.Thread.run(Thread.java:764) 

Is Deferred<Unit> supported?

I recall a snippet of a UnitConverterFactory you posted in an issue. Is is needed to support Deferred<Unit> for 204 No Content or ignored body responses?

Allow suspend + Deferred

I know suspend fun support is in the works. I assume it'd allow to directly get the expected type, which is fine.
However, supporting suspend fun in retrofit interfaces that return a Deferred<T> would allow to make an async API call that can be cancelled, since the suspend would pass the coroutineContext that contains the cancellable Job.

The use case is to make concurrent API calls, where one failure cancels the coroutine, and automatically cancels the other API calls.
Related to #7

Is the adapter working with Kotlin 1.3?

Just updated Android Studio today with new version of Kotlin. Coroutines are now stable.

For some reason my code stop working.

Could you confirm if an update is neccessary with the new import kotlinx.coroutines.* ?

Using `Deferred` type with `BehaviorDelegate`?

Hi!
How can I use the Deferred<User> type with BehaviorDelegate? for example I can not run below code :
delegate.returningResponse(response).getUser();
returningResponse(response) actually returns User.

Exception Handling

@JakeWharton
Please advise how am I supposed to handle Network exceptions when request fails? ex internet is unavailable?

In this case API call instead of Deferred<?> returns nothing and chain breaks here.
api.login(username, password).await() -- somewhere inside execution fails and in Logcat I see
<-- HTTP FAILED: java.net.UnknownHostException: Unable to resolve host "URL HERE": No address associated with hostname

`interface API {

@GET("login")

fun login(@Query("username") username: String, @Query("password") password: String): Deferred<Response<BaseNetResponse<LoginResponse>>>

}`

How to deal with exception

How to deal with exception with Deferred await error, is there any solution for no try catch for this problem?
I fix this by extension function ,thanks

Unreadable stack trace

I start my coroutine with launch and then use await on the Deferred type returned from my retrofit interface.
The stack trace of the exception thrown from the insides of adapter looks like this:

06-11 02:02:06.041 13607-13607/com.example E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example, PID: 13607
    retrofit2.HttpException: HTTP 404 Not Found
        at com.jakewharton.retrofit2.adapter.kotlin.coroutines.experimental.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:123)
        at okhttp3.RealCall$AsyncCall.execute(RealCall.java:153)
        at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

This trace is not useful since it does not trace to the original caller of await.

I'm new to coroutines and Kotlin, is there a way to make the stack trace more informative?

Can't catch exceptions

I'm trying to do something like:

class Presenter{
	fun doStuff(){
		myUiContext.launch{
			val data = withContext(backgroundContext){
				datasource.getSomeData()
			}
		}
	}
}

class Datasource{
	suspend fun getSomeData:Result<Data,Error>{
		return try{
			val data = retrofitService.getSomeDataAsync().await()
			Success(data)
		}catch(t:Throwable){
			Failure(mapToError(t))
		}
	}
}


class RetrofitService{
    @GET(Endpoint.SOME_DATA)
    fun getSomeDataAsync(): Deferred<Data>
}

And I've got Fatal exception:

E/AndroidRuntime: FATAL EXCEPTION:
retrofit2.HttpException: HTTP 404 Not Found
at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:216)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)

Why exception is not handled by try catch? What I do wrong?

Cancelling a Job, doesn't cancel the call

Hi,
I'm trying to do a search field that everytime a letter is added, the coroutine job is cancelled and a new job born to do the new query to the service.
The thing is that when I cancel the Job, if there's some problem with the service call, the Exception is not managed by anyone, so, the app crashes.

I am building the coroutine like this:

fun executeAsync(params: Params, block: suspend CoroutineScope.(T) -> Unit,
                     blockError: (CustomException?) -> Unit) {
        job = launchAsync {
            val result = buildRepoCall(params)
            launchUI { block(result) }
        }
        job?.invokeOnCompletion {
            if (it != null) blockError(CustomException(it)) //The exception is managed here.
        }
    }

and this are the coroutine wrappers:

/**
     * Launch a coroutine in a new Thread.
     * @param block The block that will be executed inside coroutine
     */
    private fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job =
            GlobalScope.async(Dispatchers.Default) {
                block()
            }

    /**
     * Launch a coroutine in the main thread.
     * @param block The block that will be executed inside coroutine
     */
    private fun launchUI(block: suspend CoroutineScope.() -> Unit): Job =
            GlobalScope.launch(Dispatchers.Main) {
                block()
            }

The buildRepoCall launches the retrofit call. At the end of the path, this functions does the call:

override suspend fun getSearchMoviesList(searchText: String, page: Int): MovieListEntity =
            MovieListMapper.toDomainObject(searchMoviesService
                    .createService().getSearchMovies(searchText, page).await())

Here I am doing the await to receive the data and transform it to a Domain Entity.

Thank you!

Allow returning a Job

For POST of PATCH requests, there is no real value to return.

Therefore I want to suggest to allow returning a kotlinx.coroutines.experimental.Job.
As Deferred implements job, the adapter could just use a Deferred<Unit> internally.

This is a follow-up of #2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.