Last active
November 24, 2022 13:08
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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