-
-
Save yitz-grocerkey/61a66a15a0c22e8ea5149484676618c9 to your computer and use it in GitHub Desktop.
| // for any errors that should be handled before being handed off to RxJava. | |
| // In other words global error logic. | |
| // An example might be 401 when not logging in | |
| import okhttp3.Interceptor | |
| import okhttp3.Response | |
| class ErrorInterceptor: Interceptor { | |
| override fun intercept(chain: Interceptor.Chain?): Response { | |
| val originalResponse = chain!!.proceed(chain.request()) | |
| if (shouldLogout(originalResponse)) { | |
| // your logout logic here | |
| // send empty response down the chain | |
| return Response.Builder().build() | |
| } | |
| return originalResponse | |
| } | |
| private fun shouldLogout(response: Response) : Boolean { | |
| if (response.isSuccessful) { | |
| return false | |
| } | |
| // 401 and auth token means that we need to logout | |
| return (response.code() == 401 && | |
| !response.headers().names().contains(AUTH_HEADER_KEY)) | |
| } | |
| } |
| import okhttp3.ResponseBody | |
| import your.package.here.ServerError | |
| import retrofit2.Converter | |
| import retrofit2.Response | |
| import retrofit2.Retrofit | |
| import timber.log.Timber | |
| import java.io.IOException | |
| class RetrofitException(private val _message: String?, | |
| private val _url: String?, | |
| private val _response: Response<*>?, | |
| private val _kind: Kind, | |
| private val _exception: Throwable?, | |
| private val _retrofit: Retrofit? | |
| ) : RuntimeException(_message, _exception) { | |
| private var _errorData : ServerError? = null | |
| companion object { | |
| fun httpError(url: String, response: Response<*>, retrofit: Retrofit): RetrofitException { | |
| val message = response.code().toString() + " " + response.message() | |
| return RetrofitException(message, url, response, Kind.HTTP, null, retrofit) | |
| } | |
| fun httpErrorWithObject(url: String, response: Response<*>, retrofit: Retrofit): RetrofitException { | |
| val message = response.code().toString() + " " + response.message() | |
| val error = RetrofitException(message, url, response, Kind.HTTP_422_WITH_DATA, null, retrofit) | |
| error.deserializeServerError() | |
| return error | |
| } | |
| fun networkError(exception: IOException): RetrofitException { | |
| return RetrofitException(exception.message, null, null, Kind.NETWORK, exception, null) | |
| } | |
| fun unexpectedError(exception: Throwable): RetrofitException { | |
| return RetrofitException(exception.message, null, null, Kind.UNEXPECTED, exception, null) | |
| } | |
| } | |
| /** The request URL which produced the error. */ | |
| fun getUrl() = _url | |
| /** Response object containing status code, headers, body, etc. */ | |
| fun getResponse() = _response | |
| /** The event kind which triggered this error. */ | |
| fun getKind() = _kind | |
| /** The Retrofit this request was executed on */ | |
| fun getRetrofit() = _retrofit | |
| /** The data returned from the server in the response body*/ | |
| fun getErrorData() : ServerError? = _errorData | |
| private fun deserializeServerError() { | |
| if (_response != null && _response.errorBody() != null) { | |
| try { | |
| _errorData = getErrorBodyAs(ServerError::class.java) | |
| } catch (e: IOException) { | |
| Timber.tag("Retrofit servererror deserialization").e(e) | |
| } | |
| } | |
| } | |
| /** | |
| * HTTP response body converted to specified `type`. `null` if there is no | |
| * response. | |
| * @throws IOException if unable to convert the body to the specified `type`. | |
| */ | |
| @Throws(IOException::class) | |
| fun <T> getErrorBodyAs(type: Class<T>): T? { | |
| if (_response == null || _response.errorBody() == null || _retrofit == null) { | |
| return null | |
| } | |
| val converter : Converter<ResponseBody, T> = | |
| _retrofit.responseBodyConverter(type, arrayOfNulls<Annotation>(0)) | |
| return converter.convert(_response.errorBody()) | |
| } | |
| enum class Kind { | |
| /** An [IOException] occurred while communicating to the server. */ | |
| NETWORK, | |
| /** A non-200 HTTP status code was received from the server. */ | |
| HTTP, | |
| HTTP_422_WITH_DATA, | |
| /** | |
| * An internal error occurred while attempting to execute a request. It is best practice to | |
| * re-throw this exception so your application crashes. | |
| */ | |
| UNEXPECTED | |
| } | |
| } |
| import io.reactivex.Observable | |
| import io.reactivex.schedulers.Schedulers | |
| import retrofit2.Call | |
| import retrofit2.CallAdapter | |
| import retrofit2.HttpException | |
| import retrofit2.Retrofit | |
| import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory | |
| import java.io.IOException | |
| import java.lang.reflect.Type | |
| class WebRequest { | |
| val retrofit: Retrofit | |
| init { | |
| val okBuilder = OkHttpClient().newBuilder() | |
| okBuilder.networkInterceptors().add(ErrorInterceptor()) | |
| retrofit = Retrofit.Builder() | |
| .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create()) | |
| .addConverterFactory(GsonConverterFactory.create()) | |
| .baseUrl(BASE_URL) | |
| .client(okBuilder.build()) | |
| .build() | |
| } | |
| } |
| // Wraps "regular" Retrofit errors in custom RetrofitException class | |
| import io.reactivex.Observable | |
| import io.reactivex.schedulers.Schedulers | |
| import retrofit2.Call | |
| import retrofit2.CallAdapter | |
| import retrofit2.HttpException | |
| import retrofit2.Retrofit | |
| import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory | |
| import java.io.IOException | |
| import java.lang.reflect.Type | |
| class RxErrorHandlingCallAdapterFactory: CallAdapter.Factory() { | |
| private val _original by lazy { | |
| RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()) | |
| } | |
| companion object { | |
| fun create() : CallAdapter.Factory = RxErrorHandlingCallAdapterFactory() | |
| } | |
| override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *> { | |
| val wrapped = _original.get(returnType, annotations, retrofit) as CallAdapter<out Any, *> | |
| return RxCallAdapterWrapper(retrofit, wrapped) | |
| } | |
| private class RxCallAdapterWrapper<R>(val _retrofit: Retrofit, | |
| val _wrappedCallAdapter: CallAdapter<R, *> | |
| ): CallAdapter<R, Observable<R>> { | |
| override fun responseType(): Type = _wrappedCallAdapter.responseType() | |
| @Suppress("UNCHECKED_CAST") | |
| override fun adapt(call: Call<R>): Observable<R> { | |
| val adapted = (_wrappedCallAdapter.adapt(call) as Observable<R>) | |
| adapted.onErrorResumeNext { throwable: Throwable -> | |
| Observable.error(asRetrofitException(throwable)) | |
| } | |
| return adapted | |
| } | |
| private fun asRetrofitException(throwable: Throwable): RetrofitException { | |
| // We had non-200 http error | |
| if (throwable is HttpException) { | |
| val response = throwable.response() | |
| if (throwable.code() == 422) { | |
| // on out api 422's get metadata in the response. Adjust logic here based on your needs | |
| return RetrofitException.httpErrorWithObject(response.raw().request().url().toString(), response, _retrofit) | |
| } else { | |
| return RetrofitException.httpError(response.raw().request().url().toString(), response, _retrofit) | |
| } | |
| } | |
| // A network error happened | |
| if (throwable is IOException) { | |
| return RetrofitException.networkError(throwable) | |
| } | |
| // We don't know what happened. We need to simply convert to an unknown error | |
| return RetrofitException.unexpectedError(throwable) | |
| } | |
| } | |
| } |
| .subscribe({ | |
| // do success action | |
| }, { it -> | |
| view.displayError(getLoginErrorMessage(it)) | |
| }) | |
| private fun getLoginErrorMessage(exception: Throwable) : String { | |
| if (exception is RetrofitException) { | |
| when (exception.getKind()) { | |
| RetrofitException.Kind.HTTP_422_WITH_DATA -> | |
| return exception.getErrorData()!!.getMessage() | |
| RetrofitException.Kind.HTTP -> | |
| return R.string.default_http_error_message | |
| RetrofitException.Kind.NETWORK -> | |
| return R.string.default_network_error_message | |
| RetrofitException.Kind.UNEXPECTED-> | |
| return R.string.default_unexpected_error_message | |
| } | |
| } | |
| return R.string.default_error_message | |
| } |
Crashing here:
val adapted = (_wrappedCallAdapter.adapt(call) as Observable<R>)
Caused by: java.lang.ClassCastException: io.reactivex.internal.operators.observable.ObservableSingleSingle cannot be cast to io.reactivex.Observable
at ...$RxCallAdapterWrapper.adapt(RxErrorHandlingCallAdapterFactory.kt:37)
at ...$RxCallAdapterWrapper.adapt(RxErrorHandlingCallAdapterFactory.kt:28)
retrofit 2.7.1
Edit: The reason was that my call was returning Single instead of Observable.
To fix this Crash :
Caused by: java.lang.ClassCastException: io.reactivex.internal.operators.observable.ObservableSingleSingle cannot be cast to io.reactivex.Observable
at ...$RxCallAdapterWrapper.adapt(RxErrorHandlingCallAdapterFactory.kt:37)
at ...$RxCallAdapterWrapper.adapt(RxErrorHandlingCallAdapterFactory.kt:28)
Solution :
override fun adapt(call: Call): Any {
return when (val result = _wrappedCallAdapter.adapt(call)) {
is Single<> -> result.onErrorResumeNext(Function { throwable -> Single.error(asOneAppServiceException(throwable)) })
is Observable<> -> result.onErrorResumeNext(Function { throwable -> Observable.error(asOneAppServiceException(throwable)) })
is Completable -> result.onErrorResumeNext(Function { throwable -> Completable.error(asOneAppServiceException(throwable)) })
is Flowable<*> -> result.onErrorResumeNext(Function { throwable -> Flowable.error(asOneAppServiceException(throwable)) })
else -> result
}
}
@abbath0767
The
returnhere can be omitted.This return statement is necessary of the
adaptmethod.