Skip to content

Instantly share code, notes, and snippets.

@Bloody-Badboy
Last active November 24, 2022 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bloody-Badboy/a4728a013065b81a383e0b74612b72aa to your computer and use it in GitHub Desktop.
Save Bloody-Badboy/a4728a013065b81a383e0b74612b72aa to your computer and use it in GitHub Desktop.
A wrapper for retrofit which wraps response in ApiResult for easier error handeling check Demo.kt for usages
sealed class ApiResult<out T> {
data class Success<out T>(val value: T) : ApiResult<T>()
data class GenericError(val throwable: Throwable? = null) : ApiResult<Nothing>()
object NetworkError : ApiResult<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[status=$value]"
is GenericError -> "GenericError[error=${throwable?.message}]"
NetworkError -> "NetworkError"
}
}
}
inline fun <reified T> ApiResult<T>.doIfNetworkError(callback: () -> Unit) {
if (this is ApiResult.NetworkError) {
callback()
}
}
inline fun <reified T> ApiResult<T>.doIfGenericError(
callback: (throwable: Throwable?) -> Unit
) {
if (this is ApiResult.GenericError) {
callback(throwable)
}
}
inline fun <reified T> ApiResult<T>.doIfSuccess(callback: (value: T) -> Unit) {
if (this is ApiResult.Success) {
callback(value)
}
}
inline fun <reified T> List<ApiResult<T>>.doIfAnyNetworkError(
callback: (result: ApiResult.NetworkError) -> Unit
) {
val index = indexOfFirst { it is ApiResult.NetworkError }
if (index >= 0) {
callback(filterIsInstance<ApiResult.NetworkError>().first())
}
}
inline fun <reified T> List<ApiResult<T>>.doIfAnyGenericError(
callback: (result: ApiResult.GenericError) -> Unit
) {
val index = indexOfFirst { it is ApiResult.GenericError }
if (index >= 0) {
callback(filterIsInstance<ApiResult.GenericError>().first())
}
}
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import java.io.IOException
suspend inline fun <T> wrapIntoApiResult(
dispatcher: CoroutineDispatcher = Dispatchers.Unconfined,
crossinline apiCall: suspend () -> T
): ApiResult<T> {
return withContext(dispatcher) {
try {
ApiResult.Success(apiCall.invoke())
} catch (throwable: Throwable) {
throwable.printStackTrace()
when (throwable) {
is IOException -> ApiResult.NetworkError
else -> {
if (throwable is retrofit2.HttpException) {
val t = HttpException(throwable)
return@withContext ApiResult.GenericError(t)
}
ApiResult.GenericError(throwable)
}
}
}
}
}
inline fun <T> CoroutineScope.wrapIntoApiResultAsync(
dispatcher: CoroutineDispatcher = Dispatchers.Unconfined,
crossinline apiCall: suspend () -> T
): Deferred<ApiResult<T>> = async(dispatcher) {
wrapIntoApiResult {
apiCall()
}
}
class HttpException(throwable: retrofit2.HttpException) :
RuntimeException(buildMessage(throwable)) {
companion object {
private fun buildMessage(throwable: retrofit2.HttpException): String {
val body = convertErrorBodyToString(throwable)
return if (body != null) {
"HTTP ${throwable.code()} ${throwable.message()} $body"
} else {
"HTTP ${throwable.code()} ${throwable.message()}"
}
}
private fun convertErrorBodyToString(throwable: retrofit2.HttpException): String? {
return try {
throwable.response()
?.errorBody()
?.string()
} catch (_: Throwable) {
null
}
}
}
}
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.create
import retrofit2.http.GET
import java.util.concurrent.TimeUnit
fun main() {
val apiService = Retrofit.Builder()
.client(
OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
})
.build()
)
.baseUrl("https://jsonplaceholder.typicode.com/")
.build()
.create<DemoApiService>()
runBlocking {
val posts = wrapIntoApiResult {
apiService.posts()
}
when (posts) {
is ApiResult.Success -> {
println(posts.value)
}
ApiResult.NetworkError -> {}
is ApiResult.GenericError -> {}
}
val errorPath = wrapIntoApiResult {
apiService.nonExistingRoute()
}
if (errorPath is ApiResult.GenericError) {
println(errorPath.throwable?.printStackTrace())
}
val results = awaitAll(
wrapIntoApiResultAsync {
apiService.posts()
},
wrapIntoApiResultAsync {
apiService.users()
}
)
results.doIfAnyGenericError {
// todo handle http/any error in batch request
}
results.doIfAnyNetworkError {
// todo handle network error in batch request
}
}
}
private interface DemoApiService {
@GET("posts")
suspend fun posts(): ResponseBody
@GET("users")
suspend fun users(): ResponseBody
@GET("404")
suspend fun nonExistingRoute(): ResponseBody
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment