How to create a call adapter for suspending functions in Retrofit?

后端 未结 3 1375
慢半拍i
慢半拍i 2020-12-05 02:35

I need to create a retrofit call adapter which can handle such network calls:

@GET(\"user\")
suspend fun getUser(): MyResponseWrapper
         


        
3条回答
  •  一个人的身影
    2020-12-05 03:08

    Here is a working example of an adapter, which automatically wraps a response to the Result wrapper. A GitHub sample is also available.

    // build.gradle
    
    ...
    dependencies {
        implementation 'com.squareup.retrofit2:retrofit:2.6.1'
        implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
        implementation 'com.google.code.gson:gson:2.8.5'
    }
    
    // test.kt
    
    ...
    sealed class Result {
        data class Success(val data: T?) : Result()
        data class Failure(val statusCode: Int?) : Result()
        object NetworkError : Result()
    }
    
    data class Bar(
        @SerializedName("foo")
        val foo: String
    )
    
    interface Service {
        @GET("bar")
        suspend fun getBar(): Result
    
        @GET("bars")
        suspend fun getBars(): Result>
    }
    
    abstract class CallDelegate(
        protected val proxy: Call
    ) : Call {
        override fun execute(): Response = throw NotImplementedError()
        override final fun enqueue(callback: Callback) = enqueueImpl(callback)
        override final fun clone(): Call = cloneImpl()
    
        override fun cancel() = proxy.cancel()
        override fun request(): Request = proxy.request()
        override fun isExecuted() = proxy.isExecuted
        override fun isCanceled() = proxy.isCanceled
    
        abstract fun enqueueImpl(callback: Callback)
        abstract fun cloneImpl(): Call
    }
    
    class ResultCall(proxy: Call) : CallDelegate>(proxy) {
        override fun enqueueImpl(callback: Callback>) = proxy.enqueue(object: Callback {
            override fun onResponse(call: Call, response: Response) {
                val code = response.code()
                val result = if (code in 200 until 300) {
                    val body = response.body()
                    Result.Success(body)
                } else {
                    Result.Failure(code)
                }
    
                callback.onResponse(this@ResultCall, Response.success(result))
            }
    
            override fun onFailure(call: Call, t: Throwable) {
                val result = if (t is IOException) {
                    Result.NetworkError
                } else {
                    Result.Failure(null)
                }
    
                callback.onResponse(this@ResultCall, Response.success(result))
            }
        })
    
        override fun cloneImpl() = ResultCall(proxy.clone())
    }
    
    class ResultAdapter(
        private val type: Type
    ): CallAdapter>> {
        override fun responseType() = type
        override fun adapt(call: Call): Call> = ResultCall(call)
    }
    
    class MyCallAdapterFactory : CallAdapter.Factory() {
        override fun get(
            returnType: Type,
            annotations: Array,
            retrofit: Retrofit
        ) = when (getRawType(returnType)) {
            Call::class.java -> {
                val callType = getParameterUpperBound(0, returnType as ParameterizedType)
                when (getRawType(callType)) {
                    Result::class.java -> {
                        val resultType = getParameterUpperBound(0, callType as ParameterizedType)
                        ResultAdapter(resultType)
                    }
                    else -> null
                }
            }
            else -> null
        }
    }
    
    /**
     * A Mock interceptor that returns a test data
     */
    class MockInterceptor : Interceptor {
        override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
            val response = when (chain.request().url().encodedPath()) {
                "/bar" -> """{"foo":"baz"}"""
                "/bars" -> """[{"foo":"baz1"},{"foo":"baz2"}]"""
                else -> throw Error("unknown request")
            }
    
            val mediaType = MediaType.parse("application/json")
            val responseBody = ResponseBody.create(mediaType, response)
    
            return okhttp3.Response.Builder()
                .protocol(Protocol.HTTP_1_0)
                .request(chain.request())
                .code(200)
                .message("")
                .body(responseBody)
                .build()
        }
    }
    
    suspend fun test() {
        val mockInterceptor = MockInterceptor()
        val mockClient = OkHttpClient.Builder()
            .addInterceptor(mockInterceptor)
            .build()
    
        val retrofit = Retrofit.Builder()
            .baseUrl("https://mock.com/")
            .client(mockClient)
            .addCallAdapterFactory(MyCallAdapterFactory())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    
        val service = retrofit.create(Service::class.java)
        val bar = service.getBar()
        val bars = service.getBars()
        ...
    }
    ...
    

提交回复
热议问题