The purpose of this library is to ease general exception handling caught from retrofit.
Most of our projects has a piece of code which wraps any exception thrown by retrofit into a custom defined one, so why don't just have a lib which does this for us?
In this case you may not need this library, which wraps all exceptions, such as network errors, parsing errors etc.
For this you can implement an interceptor, here is an example how can you do that
class NetworkErrorInterceptor(private val parser: Parser) : Interceptor {
// not this is important because otherwise it might be wrapped into an UndeclaredThrowableException even with kotlin because of reflection proxy retrofit / okhttp uses
@Throws(IOException::class, NetworkError::class)
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) // this may throw IOException
if (!response.isSuccessful) {
try {
val errorResponse = response.body?.source()?.let { errorResponseBody ->
parser.parse(errorResponseBody)
}
throw NetworkError(errorResponse)
} catch (exception: IOException) {
throw NetworkError(exception.message.orEmpty())
}
}
return response
}
}
// top level build.gradle
allprojects {
repositories {
// ...
maven {
url "https://maven.pkg.github.com/halcyonmobile/retrofit-error-wrapping"
credentials {
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
}
// https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
}
}
}
// OR
// top level build.gradle.kts
allprojects {
repositories {
// ...
maven {
url = uri("https://maven.pkg.github.com/halcyonmobile/retrofit-error-wrapping")
credentials {
username = extra.properties["GITHUB_USERNAME"] as String? ?: System.getenv("GITHUB_USERNAME")
password = extra.properties["GITHUB_TOKEN"] as String? ?: System.getenv("GITHUB_TOKEN")
}
// https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
}
}
}
Note: you only need one maven declaration with "halcyonmobile/{specific}", every other package will be accessible.
implementation "com.halcyonmobile.retrofit-error-parsing:retrofit-error-parsing:latest-version"
Retrofit.Builder()
//...
.addCallAdapterFactory(ErrorWrappingAndParserCallAdapterFactory())
.build()
interface Service {
@WrapIntoNetworkException
@GET("alma")
suspend fun networkErrorWrapping(): Unit?
}
- Now this function will throw NetworkException instead.
then you should set the workWithoutAnnotation flag for the call adapter factory
Retrofit.Builder()
//...
.addCallAdapterFactory(ErrorWrappingAndParserCallAdapterFactory(workWithoutAnnotation = true))
.build()
then add a NetworkExceptionConverter to the call adapter factory
class CustomException: Throwable()
class NetworkExceptionToCustomException : NetworkExceptionConverter {
override fun convert(networkException: NetworkException): RuntimeException = CustomException()
}
// ...
Retrofit.Builder()
//...
.addCallAdapterFactory(ErrorWrappingAndParserCallAdapterFactory(NetworkExceptionToCustomException()))
.build()
then use the @ParseError annotation instead
interface Service {
@ParsedError(value = GeneralError::class)
@GET("alma")
suspend fun exceptionParsing(): Unit?
Now your NetworkException.parsedError will be a GeneralError if it could be parsed. If not then it will be simply null. Note: you have to cast to your model type. Note2: the errorBody is still contained in the NetworkException as string.
then use CallAdapterFactoryWrapper such as
Retrofit.Builder()
//...
.addCallAdapterFactory(CallAdapterFactoryWrapper(RxJava2CallAdapterFactory.create()))
.build()
Now you are free to use RxObservables while getting only NetworkExceptions in the onError.
For this you can implement the ErrorParsingFailureLogger and attach it to the CallAdapter
There is a specific NetworkException called NoNetworkException this is thrown when retrofit couldn't look up the server's address based on the url provided, meaning the url is either no longer valid or you simply can't connect to the DNS, you don't have network connection. This should be sufficient for no-internet error handling in most cases.
Yes you can, however in this case you are responsible for providing No-Internet exception
You can use the other constructors of the ErrorWrappingAndParserCallAdapterFactory. You can implement ErrorResponseToExceptionConverter.Factory and provide your own ErrorResponseToExceptionConverter for every type of response you receive. You will get the raw response if it wasn't successful or the raw throwable if some exception occurred while processing the request.
In case of parsed model to Exception you can use ParsedErrorToExceptionConverter.Factory. You can provide a different ParsedErrorToExceptionConverter for each exception being parsed. You can also use DelegateFactory to add your ParsedErrorToExceptionConverter for each specific type.
Copyright (c) 2020 Halcyon Mobile.
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
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.