web-dev-qa-db-ja.com

Retrofit 2.6.0:カスタムコルーチンCallAdapterFactory

APIでカスタムエラー処理を行う必要があり、新しいバージョンのRetrofitでコルーチンを使用したいと考えていました。 Deferredを使用する必要がなくなったため、私たち自身のJake Whartonが1か月前にredditでこれを書いた

enter image description here

https://github.com/square/retrofit/blob/master/samples/src/main/Java/com/example/retrofit/RxJavaObserveOnMainThread.Java

しかし、CallAdapterFactoryを適切に作成するのに問題があります。

具体的には、「組み込みファクトリーにデリゲートしてから、値をシールされたクラスでラップする」とはわかりません。

このセットアップを使用している人はいますか?

これが現在のコードです

sealed class Results<out T: Any> {
    class Success<out T: Any>(val response: T): Results<T>()
    class Failure(val message: String, val serverError: ServerError?): Results<Nothing>()
    object NetworkError: Results<Nothing>()
}


class ResultsCallAdapterFactory private constructor() : CallAdapter.Factory() {

    companion object {
        @JvmStatic
        fun create() = ResultsCallAdapterFactory()
    }

    override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
        return try {
            val enclosedType = returnType as ParameterizedType
            val responseType = getParameterUpperBound(0, enclosedType)
            val rawResultType = getRawType(responseType)
            val delegate: CallAdapter<Any,Any> = retrofit.nextCallAdapter(this,returnType,annotations) as CallAdapter<Any,Any>
            if(rawResultType != Results::class.Java)
                null
            else {
                object: CallAdapter<Any,Any>{
                    override fun adapt(call: Call<Any>): Any {
                         val response = delegate.adapt(call)
                        //What should happen here?
                        return response
                    }

                    override fun responseType(): Type {
                        return delegate.responseType()
                    }

                }
            }
        } catch (e: ClassCastException) {
            null
        }

    }
}
8
Leonardo Deleon

これが実際の例です。 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()
    ...
}
...
9
Valeriy Katkov