Skip to content

Commit 674262a

Browse files
bugfix/error_signing_redirect: Added error handling
* Added a retrofit exception that transforms all the retrofit rx calls to the retrofit exception. Retrofit exception is of 3 types, http, network and unexpected. * Added WebViewException, RetrofitException and EmptyResultsException. WebViewException is called when the webview fails to load an url. EmptyResultsException is thrown in case the data retrieved from both database and network is null or empty. * Added a base fragment, namely, BaseViewModelFragment where we handle the error received from the viewmodel by overriding onError method. * Added error handling when login fails. * Replaced all this from methods like viewModel.obj.observer(this, ...) with viewLifecycleOwner. * Created a few utility methods in Utils. * Improvd Result class so now it supports success and error checks. * Added error codes for not authorized, unknown and not found. * Added some error messages to the strings.xml. * Added logs for unknown actions. * Added todos for not fixed errors.
1 parent b881b50 commit 674262a

33 files changed

+675
-176
lines changed

app/src/main/java/ro/code4/monitorizarevot/data/dao/FormsDao.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ interface FormsDao {
114114
pollingStationNumber: Int,
115115
formId: Int,
116116
synced: Boolean = true
117-
)
117+
): Completable
118118

119119
@Query("UPDATE question SET hasNotes=:hasNotes WHERE id=:questionId")
120120
fun updateQuestionWithNotes(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ro.code4.monitorizarevot.exceptions
2+
3+
import java.lang.Exception
4+
5+
class EmptyResultsException(message: String, thr: Throwable? = null) : Exception(message, thr)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package ro.code4.monitorizarevot.exceptions
2+
3+
object ErrorCodes {
4+
const val UNAUTHORIZED = 401
5+
const val NOT_FOUND = 404
6+
const val UNKNOWN = 400
7+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package ro.code4.monitorizarevot.exceptions
2+
3+
import okhttp3.ResponseBody
4+
import retrofit2.Converter
5+
import retrofit2.Response
6+
import retrofit2.Retrofit
7+
import java.io.IOException
8+
9+
10+
/**
11+
* Exception that is retrieved from retrofit. It is of three types, http, network and unexpected.
12+
*/
13+
open class RetrofitException internal constructor(
14+
message: String?,
15+
/**
16+
* RobResponse object containing status code, headers, body, etc.
17+
*/
18+
val response: Response<*>?,
19+
/**
20+
* The event kind which triggered this error.
21+
*/
22+
val kind: Kind,
23+
val exception: Throwable?,
24+
/**
25+
* The Retrofit this request was executed on
26+
*/
27+
val retrofit: Retrofit?
28+
) :
29+
RuntimeException(message, exception) {
30+
/**
31+
* Identifies the event kind which triggered a [RetrofitException].
32+
*/
33+
enum class Kind {
34+
/**
35+
* An [IOException] occurred while communicating to the server.
36+
*/
37+
NETWORK,
38+
39+
/**
40+
* A non-200 HTTP status code was received from the server.
41+
*/
42+
HTTP,
43+
44+
/**
45+
* An internal error occurred while attempting to execute a request. It is best practice to
46+
* re-throw this exception so your application crashes.
47+
*/
48+
UNEXPECTED
49+
}
50+
51+
/**
52+
* HTTP response body converted to specified `type`. `null` if there is no
53+
* response.
54+
*
55+
* @param type
56+
* @throws IOException if unable to convert the body to the specified `type`.
57+
*/
58+
@Throws(IOException::class)
59+
fun <T> getErrorBodyAs(type: Class<*>?): T? {
60+
if (response?.errorBody() == null) {
61+
return null
62+
}
63+
val converter: Converter<ResponseBody?, T> =
64+
retrofit!!.responseBodyConverter(type, arrayOfNulls(0))
65+
return converter.convert(response.errorBody())
66+
}
67+
68+
companion object {
69+
fun httpError(
70+
response: Response<*>,
71+
retrofit: Retrofit?
72+
): RetrofitException {
73+
val message = response.code().toString() + " " + response.message()
74+
return RetrofitException(
75+
message,
76+
response,
77+
Kind.HTTP,
78+
null,
79+
retrofit
80+
)
81+
}
82+
83+
fun networkError(exception: IOException): RetrofitException {
84+
return RetrofitException(
85+
exception.message,
86+
null,
87+
Kind.NETWORK,
88+
exception,
89+
null
90+
)
91+
}
92+
93+
fun unexpectedError(exception: Throwable): RetrofitException {
94+
return RetrofitException(
95+
exception.message,
96+
null,
97+
Kind.UNEXPECTED,
98+
exception,
99+
null
100+
)
101+
}
102+
}
103+
104+
}
105+
106+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ro.code4.monitorizarevot.exceptions
2+
3+
import java.lang.Exception
4+
5+
/**
6+
* Exception thrown when there is an issue with webview, loading paage, etc.
7+
*/
8+
class WebViewException(override val message: String, val code: Int = -1) : Exception(message)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ro.code4.monitorizarevot.extensions
2+
3+
import retrofit2.HttpException
4+
import retrofit2.Response
5+
6+
fun <T> Response<T>.successOrThrow(): Boolean {
7+
if (!isSuccessful) throw HttpException(this)
8+
return true
9+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package ro.code4.monitorizarevot.extensions
2+
3+
import io.reactivex.*
4+
import retrofit2.Call
5+
import retrofit2.CallAdapter
6+
import retrofit2.HttpException
7+
import retrofit2.Retrofit
8+
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
9+
import ro.code4.monitorizarevot.exceptions.RetrofitException
10+
import ro.code4.monitorizarevot.exceptions.RetrofitException.Companion.httpError
11+
import ro.code4.monitorizarevot.exceptions.RetrofitException.Companion.networkError
12+
import ro.code4.monitorizarevot.exceptions.RetrofitException.Companion.unexpectedError
13+
import java.io.IOException
14+
import java.lang.reflect.ParameterizedType
15+
import java.lang.reflect.Type
16+
17+
/**
18+
* Rxjava error handling CallAdapter factory. This class ensures the mapping of errors to
19+
* one of the following exceptions: http, network or unexpected exceptions.
20+
*/
21+
class RxErrorHandlingCallAdapterFactory private constructor() : CallAdapter.Factory() {
22+
private val original: RxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create()
23+
24+
override fun get(
25+
returnType: Type, annotations: Array<Annotation>,
26+
retrofit: Retrofit
27+
): CallAdapter<*, *> {
28+
return RxCallAdapterWrapper(
29+
returnType,
30+
retrofit,
31+
(original.get(returnType, annotations, retrofit) as CallAdapter<Any, Any>)
32+
)
33+
}
34+
35+
internal inner class RxCallAdapterWrapper(
36+
private val returnType: Type,
37+
private val retrofit: Retrofit,
38+
private val wrapped: CallAdapter<Any, Any>
39+
) :
40+
CallAdapter<Any, Any> {
41+
override fun responseType(): Type {
42+
return wrapped.responseType()
43+
}
44+
45+
override fun adapt(call: Call<Any>): Any? {
46+
val rawType = getRawType(returnType)
47+
48+
val isFlowable = rawType == Flowable::class.java
49+
val isSingle = rawType == Single::class.java
50+
val isMaybe = rawType == Maybe::class.java
51+
val isCompletable = rawType == Completable::class.java
52+
if (rawType != Observable::class.java && !isFlowable && !isSingle && !isMaybe) {
53+
return null
54+
}
55+
if (returnType !is ParameterizedType) {
56+
val name = if (isFlowable)
57+
"Flowable"
58+
else if (isSingle) "Single" else if (isMaybe) "Maybe" else "Observable"
59+
throw IllegalStateException(
60+
name
61+
+ " return type must be parameterized"
62+
+ " as "
63+
+ name
64+
+ "<Foo> or "
65+
+ name
66+
+ "<? extends Foo>"
67+
)
68+
}
69+
70+
if (isFlowable) {
71+
return (wrapped.adapt(call) as Flowable<*>).onErrorResumeNext { throwable: Throwable ->
72+
Flowable.error(asRetrofitException(throwable))
73+
}
74+
}
75+
if (isSingle) {
76+
return (wrapped.adapt(call) as Single<*>).onErrorResumeNext { throwable ->
77+
Single.error(asRetrofitException(throwable))
78+
}
79+
}
80+
if (isMaybe) {
81+
return (wrapped.adapt(call) as Maybe<*>).onErrorResumeNext { throwable: Throwable ->
82+
Maybe.error(asRetrofitException(throwable))
83+
}
84+
}
85+
if (isCompletable) {
86+
return (wrapped.adapt(call) as Completable).onErrorResumeNext { throwable ->
87+
Completable.error(asRetrofitException(throwable))
88+
}
89+
}
90+
return (wrapped.adapt(call) as Observable<*>).onErrorResumeNext { throwable: Throwable ->
91+
Observable.error(asRetrofitException(throwable))
92+
}
93+
}
94+
95+
private fun asRetrofitException(throwable: Throwable): RetrofitException {
96+
if (throwable is HttpException) {
97+
val response = throwable.response()
98+
return httpError(response!!, retrofit)
99+
} else if (throwable is IOException) {
100+
return networkError(throwable)
101+
}
102+
return unexpectedError(throwable)
103+
}
104+
}
105+
106+
companion object {
107+
const val TAG = "RxErrorHandlingCallAdapterFactory"
108+
109+
fun create(): CallAdapter.Factory {
110+
return RxErrorHandlingCallAdapterFactory()
111+
}
112+
}
113+
}
114+

app/src/main/java/ro/code4/monitorizarevot/helper/APIError400.kt

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package ro.code4.monitorizarevot.helper
22

3+
34
/**
4-
* .:.:.:. Created by @henrikhorbovyi on 13/10/19 .:.:.:.
5+
6+
* Class that encapsulates successful result with a value of type [T] or
7+
* a failure with a [Throwable] exception.
58
*/
69
sealed class Result<out T> {
7-
class Failure(val error: Throwable, val message: String = "") : Result<Nothing>()
8-
class Success<out T>(val data: T? = null) : Result<T>()
10+
data class Error(val exception: Throwable) : Result<Nothing>()
11+
data class Success<out T>(val data: T? = null) : Result<T>()
912
object Loading : Result<Nothing>()
1013

14+
fun exceptionOrNull(): Throwable? =
15+
when (this) {
16+
is Error -> exception
17+
else -> null
18+
}
1119

1220
fun handle(
1321
onSuccess: (T?) -> Unit = {},
@@ -16,8 +24,32 @@ sealed class Result<out T> {
1624
) {
1725
when (this) {
1826
is Success -> onSuccess(data)
19-
is Failure -> onFailure(error)
27+
is Error -> onFailure(exception)
2028
is Loading -> onLoading()
2129
}
2230
}
23-
}
31+
32+
override fun toString(): String {
33+
return when (this) {
34+
is Success<*> -> "Success[data=$data]"
35+
is Error -> "Error[exception=$exception]"
36+
Loading -> "Loading"
37+
}
38+
}
39+
}
40+
41+
val Result<*>.succeeded
42+
get() = this is Result.Success && data != null
43+
44+
val Result<*>.error
45+
get() = this is Result.Error
46+
47+
inline fun <R, T : R> Result<T>.getOrThrow(onFailure: (exception: Throwable) -> R): R {
48+
return when (val exception = exceptionOrNull()) {
49+
null -> data as T
50+
else -> onFailure(exception)
51+
}
52+
}
53+
54+
val <T> Result<T>.data: T?
55+
get() = (this as? Result.Success)?.data

0 commit comments

Comments
 (0)