Skip to content

Commit 308d835

Browse files
#5 - configurando tratamento de erro no API
1 parent 5f9a911 commit 308d835

File tree

17 files changed

+423
-16
lines changed

17 files changed

+423
-16
lines changed

buildSrc/src/main/java/Config.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ object Config {
99
const val packageName = "com.codandotv.streamplayerapp."
1010

1111
object BuildField {
12-
const val host_debug = "\"https://api.themoviedb.org/\""
13-
const val host_release = "\"https://api.themoviedb.org/\""
12+
13+
const val host_debug = "\"https://demo5700495.mockable.io/\""
14+
const val host_release = "\"https://demo5700495.mockable.io/\""
1415
}
1516
}

core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/di/NetworkModule.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.codandotv.streamplayerapp.core_networking.di
22

33
import com.codandotv.streamplayerapp.core_networking.BuildConfig
4+
import com.codandotv.streamplayerapp.core_networking.handleError.coroutines.NetworkResponseAdapter
5+
import com.codandotv.streamplayerapp.core_networking.handleError.coroutines.NetworkResponseAdapterFactory
46
import com.squareup.moshi.Moshi
57
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
68
import okhttp3.Interceptor
@@ -57,7 +59,7 @@ object NetworkModule {
5759

5860
single {
5961
provideOkhttp(
60-
get(QualifierAuthInterceptor),
62+
// get(QualifierAuthInterceptor),
6163
get(QualifierLoggerInterceptor),
6264
)
6365
}
@@ -84,5 +86,6 @@ object NetworkModule {
8486
.baseUrl(baseUrl)
8587
.client(okHttpClient)
8688
.addConverterFactory(MoshiConverterFactory.create(moshi))
89+
.addCallAdapterFactory(NetworkResponseAdapterFactory(moshi))
8790
.build()
8891
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.codandotv.streamplayerapp.core_networking.handleError
2+
3+
/**
4+
* Base Class for handling errors/failures/exceptions.
5+
*/
6+
sealed class Failure(val code: Int? = -1, val errorMessage: String) : java.lang.Exception() {
7+
data class NoDataContent(val codeStatus: Int? = null) : Failure(codeStatus, "No data content")
8+
data class ServerError(val codeStatus: Int? = null) : Failure(codeStatus, "Server error!")
9+
data class GenericError(val codeStatus: Int? = -1100,
10+
private val msg: String? = null) : Failure(codeStatus, msg
11+
?: MSG_DEFAULT)
12+
13+
data class NetworkError(val codeStatus: Int? = -1200,
14+
private val throwable: Throwable) : Failure(codeStatus, "Sem conexão. Verifique o wifi ou dados móveis e tente novamente em alguns instantes.")
15+
16+
data class UnknownError(val codeStatus: Int? = null,
17+
private val throwable: Throwable? = Exception()) : Failure(codeStatus, throwable?.message
18+
?: MSG_DEFAULT)
19+
20+
data class UnexpectedApiException(val codeStatus: Int? = -1011,
21+
private val throwable: Throwable? = Exception()) : Failure(codeStatus, throwable?.message
22+
?: MSG_DEFAULT)
23+
24+
data class ClientException(val codeStatus: Int? = -1011,
25+
private val throwable: Throwable? = Exception()) : Failure(codeStatus, throwable?.message
26+
?: MSG_DEFAULT)
27+
28+
data class UnparsableResponseException(val codeStatus: Int? = -1012,
29+
private val throwable: Throwable? = Exception()) : Failure(codeStatus, throwable?.message
30+
?: MSG_DEFAULT)
31+
32+
companion object {
33+
private const val MSG_DEFAULT = "Não foi possível concluir. Estamos trabalhando para resolver. Tente novamente em alguns instantes."
34+
}
35+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.codandotv.streamplayerapp.core_networking.handleError
2+
3+
sealed class NetworkResponse<out T> {
4+
data class Success<out T>(
5+
val value: T
6+
) : NetworkResponse<T>()
7+
8+
data class Error(
9+
val body: Any? = null,
10+
@Transient
11+
val exception: Failure? = null
12+
) : NetworkResponse<Nothing>()
13+
}
14+
15+
fun <T> NetworkResponse<T>.toResult(): Result<T> =
16+
when (this) {
17+
is NetworkResponse.Success -> {
18+
Result.success(this.value)
19+
}
20+
is NetworkResponse.Error -> {
21+
Result.failure(this.exception ?: Failure.GenericError())
22+
}
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
package com.codandotv.streamplayerapp.core_networking.handleError
3+
4+
inline fun <T> Result<T>.onError(action: (exception: Failure) -> Unit) {
5+
if (isFailure && exceptionOrNull() is Failure) {
6+
val error = exceptionOrNull() as Failure
7+
action(error)
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.codandotv.streamplayerapp.core_networking.handleError.coroutines
2+
3+
import com.squareup.moshi.Moshi
4+
import retrofit2.Call
5+
import retrofit2.CallAdapter
6+
import java.lang.reflect.Type
7+
8+
class NetworkResponseAdapter(
9+
private val responseType: Type,
10+
private val moshi: Moshi
11+
): CallAdapter<Type, Any> {
12+
13+
override fun responseType(): Type = responseType
14+
override fun adapt(call: Call<Type>) = NetworkResponseCall(call,moshi)
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.codandotv.streamplayerapp.core_networking.handleError.coroutines
2+
3+
import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4+
import com.squareup.moshi.Moshi
5+
import retrofit2.Call
6+
import retrofit2.CallAdapter
7+
import retrofit2.Retrofit
8+
import java.lang.reflect.ParameterizedType
9+
import java.lang.reflect.Type
10+
11+
class NetworkResponseAdapterFactory(private val moshi : Moshi): CallAdapter.Factory() {
12+
override fun get(returnType: Type,
13+
annotations: Array<Annotation>,
14+
retrofit: Retrofit
15+
): CallAdapter<*, *>? {
16+
return try {
17+
// suspend functions wrap the response type in `Call`
18+
if (Call::class.java != getRawType(returnType)) {
19+
return null
20+
}
21+
22+
// check first that the return type is `ParameterizedType`
23+
check(returnType is ParameterizedType) {
24+
"return type must be parameterized as Call<NetworkResponse<<Foo>> or Call<NetworkResponse<out Foo>>"
25+
}
26+
27+
// get the response type inside the `Call` type
28+
val responseType = getParameterUpperBound(0, returnType)
29+
30+
// if the response type is not ApiResponse then we can't handle this type, so we return null
31+
if (getRawType(responseType) != NetworkResponse::class.java) {
32+
return null
33+
}
34+
35+
// the response type is ApiResponse and should be parameterized
36+
check(responseType is ParameterizedType) { "Response must be parameterized as NetworkResponse<Foo> or NetworkResponse<out Foo>" }
37+
38+
val successBodyType = getParameterUpperBound(0, responseType)
39+
40+
return NetworkResponseAdapter(successBodyType,moshi)
41+
} catch (ex: ClassCastException) {
42+
null
43+
}
44+
}
45+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package com.codandotv.streamplayerapp.core_networking.handleError.coroutines
2+
3+
import com.codandotv.streamplayerapp.core_networking.handleError.Failure
4+
import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
5+
import com.squareup.moshi.Moshi
6+
import okhttp3.Request
7+
import okhttp3.ResponseBody
8+
import okio.Timeout
9+
import retrofit2.Call
10+
import retrofit2.Callback
11+
import retrofit2.HttpException
12+
import retrofit2.Response
13+
import java.io.IOException
14+
15+
class NetworkResponseCall<T>(
16+
proxy: Call<T>,
17+
private val moshi: Moshi
18+
) :
19+
CallDelegate<T, NetworkResponse<T>>(proxy) {
20+
21+
override fun enqueueImpl(callback: Callback<NetworkResponse<T>>) =
22+
proxy.enqueue(object : Callback<T> {
23+
override fun onResponse(call: Call<T>, response: Response<T>) {
24+
val body = response.body()
25+
val code = response.code()
26+
val error = response.errorBody()
27+
if (response.isSuccessful) {
28+
responseSuccessful(body, callback, code)
29+
} else {
30+
responseError(callback,error, code)
31+
}
32+
}
33+
34+
override fun onFailure(call: Call<T>, throwable: Throwable) {
35+
val networkResponse = when (throwable) {
36+
is IOException -> Failure.NetworkError(throwable = throwable)
37+
is HttpException -> convertException(throwable)
38+
else -> Failure.GenericError(msg = throwable.message)
39+
}
40+
callback.onResponse(
41+
this@NetworkResponseCall,
42+
Response.success(NetworkResponse.Error(exception = networkResponse))
43+
)
44+
}
45+
46+
47+
private fun convertException(exception: HttpException): Failure {
48+
return try {
49+
val response = getErrorResponse(exception)
50+
Failure.UnexpectedApiException(throwable = response.exception)
51+
} catch (ex: Failure.ClientException) {
52+
ex
53+
}catch (ex : Failure.UnparsableResponseException){
54+
ex
55+
}
56+
}
57+
58+
private fun getErrorResponse(ex: HttpException): NetworkResponse.Error {
59+
val error = ex.response()?.errorBody()?.string()
60+
if (error?.isEmpty() != false) {
61+
throw Failure.ClientException(throwable = ex)
62+
}
63+
return parseError(error, ex)
64+
}
65+
66+
private fun parseError(error: String, ex: HttpException): NetworkResponse.Error {
67+
try {
68+
return moshi
69+
.adapter(NetworkResponse.Error::class.java)
70+
.fromJson(error)!!
71+
} catch (e: Exception) {
72+
throw Failure.UnparsableResponseException(throwable = ex)
73+
}
74+
}
75+
})
76+
77+
private fun responseSuccessful(
78+
body: T?,
79+
callback: Callback<NetworkResponse<T>>,
80+
code: Int
81+
) {
82+
if (body != null) {
83+
callback.onResponse(
84+
this@NetworkResponseCall,
85+
Response.success(NetworkResponse.Success(body))
86+
)
87+
} else {
88+
callback.onResponse(
89+
this@NetworkResponseCall,
90+
Response.success(
91+
NetworkResponse.Error(
92+
exception = Failure.NoDataContent(
93+
code
94+
)
95+
)
96+
)
97+
)
98+
}
99+
}
100+
101+
private fun responseError(callback: Callback<NetworkResponse<T>>, error: ResponseBody?, code: Int) {
102+
val errorBody = when {
103+
error == null -> null
104+
error.contentLength() == 0L -> null
105+
else -> try {
106+
moshi
107+
.adapter(NetworkResponse.Error::class.java)
108+
.fromJson(error.string())
109+
} catch (ex: Exception) {
110+
null
111+
}
112+
}
113+
if (errorBody != null) {
114+
callback.onResponse(
115+
this@NetworkResponseCall,
116+
Response.success(
117+
NetworkResponse.Error(
118+
body = errorBody,
119+
exception = Failure.ServerError(code)
120+
)
121+
)
122+
)
123+
} else {
124+
callback.onResponse(
125+
this@NetworkResponseCall,
126+
Response.success(
127+
NetworkResponse.Error(
128+
exception = Failure.UnknownError(
129+
code
130+
)
131+
)
132+
)
133+
)
134+
}
135+
}
136+
137+
138+
override fun cloneImpl(): Call<NetworkResponse<T>> =
139+
NetworkResponseCall(proxy.clone(), moshi)
140+
}
141+
142+
abstract class CallDelegate<TIn, TOut>(
143+
protected val proxy: Call<TIn>
144+
) : Call<TOut> {
145+
override fun execute(): Response<TOut> = throw NotImplementedError()
146+
override final fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
147+
override final fun clone(): Call<TOut> = cloneImpl()
148+
149+
override fun cancel() = proxy.cancel()
150+
override fun request(): Request = proxy.request()
151+
override fun isExecuted() = proxy.isExecuted
152+
override fun isCanceled() = proxy.isCanceled
153+
154+
abstract fun enqueueImpl(callback: Callback<TOut>)
155+
abstract fun cloneImpl(): Call<TOut>
156+
override fun timeout(): Timeout = proxy.timeout()
157+
}

0 commit comments

Comments
 (0)