为了账号安全,请及时绑定邮箱和手机立即绑定

Retrofit中如何创建挂起函数的调用适配器?

Retrofit中如何创建挂起函数的调用适配器?

HUWWW 2023-01-05 17:07:15

我需要创建一个可以处理此类网络调用的改造调用适配器:

@GET("user")
suspend fun getUser(): MyResponseWrapper<User>

我希望它在不使用Deferred. 我已经成功实现了 using Deferred,它可以处理以下方法:

@GET("user")
fun getUser(): Deferred<MyResponseWrapper<User>>

但我希望能够使函数成为暂停函数并删除Deferred包装器。

使用挂起函数,Retrofit 就像Call在返回类型周围有一个包装器一样工作,因此suspend fun getUser(): User被视为fun getUser(): Call<User>

我的实现

我试图创建一个调用适配器来处理这个问题。到目前为止,这是我的实现:

工厂

class MyWrapperAdapterFactory : CallAdapter.Factory() {


    override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {


        val rawType = getRawType(returnType)


        if (rawType == Call::class.java) {


            returnType as? ParameterizedType

                ?: throw IllegalStateException("$returnType must be parameterized")


            val containerType = getParameterUpperBound(0, returnType)


            if (getRawType(containerType) != MyWrapper::class.java) {

                return null

            }


            containerType as? ParameterizedType

                ?: throw IllegalStateException("MyWrapper must be parameterized")


            val successBodyType = getParameterUpperBound(0, containerType)

            val errorBodyType = getParameterUpperBound(1, containerType)


            val errorBodyConverter = retrofit.nextResponseBodyConverter<Any>(

                null,

                errorBodyType,

                annotations

            )


            return MyWrapperAdapter<Any, Any>(successBodyType, errorBodyConverter)

        }

        return null

    }


查看完整描述

3 回答

?
陪伴而非守候

TA贡献1532条经验 获得超8个赞

这是一个适配器的工作示例,它自动将响应包装到Result包装器。还提供了 GitHub 示例。

// 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<out T> {

    data class Success<T>(val data: T?) : Result<T>()

    data class Failure(val statusCode: Int?) : Result<Nothing>()

    object NetworkError : Result<Nothing>()

}


data class Bar(

    @SerializedName("foo")

    val foo: String

)


interface Service {

    @GET("bar")

    suspend fun getBar(): Result<Bar>


    @GET("bars")

    suspend fun getBars(): Result<List<Bar>>

}


abstract class CallDelegate<TIn, TOut>(

    protected val proxy: Call<TIn>

) : Call<TOut> {

    override fun execute(): Response<TOut> = throw NotImplementedError()

    override final fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)

    override final fun clone(): Call<TOut> = 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<TOut>)

    abstract fun cloneImpl(): Call<TOut>

}


class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, Result<T>>(proxy) {

    override fun enqueueImpl(callback: Callback<Result<T>>) = proxy.enqueue(object: Callback<T> {

        override fun onResponse(call: Call<T>, response: Response<T>) {

            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>, 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<Type, Call<Result<Type>>> {

    override fun responseType() = type

    override fun adapt(call: Call<Type>): Call<Result<Type>> = ResultCall(call)

}


class MyCallAdapterFactory : CallAdapter.Factory() {

    override fun get(

        returnType: Type,

        annotations: Array<Annotation>,

        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()

    ...

}

...



查看完整回答
反对 回复 2023-01-05
?
米脂

TA贡献1597条经验 获得超3个赞

当您使用Retrofit 2.6.0协程时,您不再需要包装器。它应该如下所示:


@GET("user")

suspend fun getUser(): User

你不再需要MyResponseWrapper了,当你调用它时,它应该看起来像


runBlocking {

   val user: User = service.getUser()

}

要进行改造Response,您可以执行以下操作:


@GET("user")

suspend fun getUser(): Response<User>

您也不需要MyWrapperAdapterFactory或MyWrapperAdapter。


希望这回答了你的问题!


编辑 CommonsWare@ 在上面的评论中也提到了这一点


编辑 处理错误可能如下:


sealed class ApiResponse<T> {

    companion object {

        fun <T> create(response: Response<T>): ApiResponse<T> {

            return if(response.isSuccessful) {

                val body = response.body()

                // Empty body

                if (body == null || response.code() == 204) {

                    ApiSuccessEmptyResponse()

                } else {

                    ApiSuccessResponse(body)

                }

            } else {

                val msg = response.errorBody()?.string()

                val errorMessage = if(msg.isNullOrEmpty()) {

                    response.message()

                } else {

                    msg

                }

                ApiErrorResponse(errorMessage ?: "Unknown error")

            }

        }

    }

}


class ApiSuccessResponse<T>(val data: T): ApiResponse<T>()

class ApiSuccessEmptyResponse<T>: ApiResponse<T>()

class ApiErrorResponse<T>(val errorMessage: String): ApiResponse<T>()

您只需要使用响应调用创建,ApiResponse.create(response)它应该返回正确的类型。还可以在此处添加更高级的场景,如果它不仅仅是一个纯字符串,则通过解析错误。


查看完整回答
反对 回复 2023-01-05
?
白猪掌柜的

TA贡献1640条经验 获得超9个赞

当您使用Retrofit 2.6.0协程时,您不再需要包装器。它应该如下所示:


@GET("user")

suspend fun getUser(): User

你不再需要MyResponseWrapper了,当你调用它时,它应该看起来像


runBlocking {

   val user: User = service.getUser()

}

要进行改造Response,您可以执行以下操作:


@GET("user")

suspend fun getUser(): Response<User>

您也不需要MyWrapperAdapterFactory或MyWrapperAdapter。


希望这回答了你的问题!


编辑 CommonsWare@ 在上面的评论中也提到了这一点


编辑 处理错误可能如下:


sealed class ApiResponse<T> {

    companion object {

        fun <T> create(response: Response<T>): ApiResponse<T> {

            return if(response.isSuccessful) {

                val body = response.body()

                // Empty body

                if (body == null || response.code() == 204) {

                    ApiSuccessEmptyResponse()

                } else {

                    ApiSuccessResponse(body)

                }

            } else {

                val msg = response.errorBody()?.string()

                val errorMessage = if(msg.isNullOrEmpty()) {

                    response.message()

                } else {

                    msg

                }

                ApiErrorResponse(errorMessage ?: "Unknown error")

            }

        }

    }

}


class ApiSuccessResponse<T>(val data: T): ApiResponse<T>()

class ApiSuccessEmptyResponse<T>: ApiResponse<T>()

class ApiErrorResponse<T>(val errorMessage: String): ApiResponse<T>()

您只需要使用响应调用创建,ApiResponse.create(response)它应该返回正确的类型。还可以在此处添加更高级的场景,如果它不仅仅是一个纯字符串,则通过解析错误。


查看完整回答
反对 回复 2023-01-05

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信