Retrofit with RxJava - custom failure data class - android

I have an API which sends me errors with a custom JSON under 500 error
so, here's my Api Interface:
#POST("${API_PREFIX}method")
fun callForIt(#Body request: MyRequest) : Single<MyResponse>
And here's how I call it:
api.callForIt(request)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
// Here I get perfect MyResponse object
},
{
// And here I get only throwable, but need to get info from the json
})
So, what I want is to describe a custom data class like
data class ErrorResponse(
val type: String,
val fatal: Boolean,
val msg: String
// etc
)
because the server is sending me valuable info in JSON and I need to obtain it, so is there any way to read it from onFailure()?

{ t: Throwable ->
if (t is HttpException) {
val errorMsg = t.response()!!.errorBody()!!.string()
val errorResponse = Gson().fromJson(errorMsg, ErrorResponse::class)
// handle the error with errorResponse...
}
else {
// handle the error with code >=500...
}
}

Related

How to handle error response return from server api in kotlin+retrofit+coroutine+mvvm

I have two different response from the same endpoint. One being the actual success result data model and one being an error response model. Both json structure like this:
SuccessResponse:
{
"result":{
"id":1,
"name_en":"Stack Over Flow",
"summary":"Stack Overflow is the largest, most trusted online community for developers to learn, share​ ​their programming ​knowledge, and build their careers."
}
}
ErrorResponse:
{
"message": "Login Failed"
}
I can handle the success response but I can't show the error message what I get from the server. I have tried many ways but I can't do this.
Here my I share my some aspect what I did
MainViewModel.kt
var job: Job? = null
val myDataResponse: MutableLiveData<HandleResource<DataResponse>> =MutableLiveData()
fun myData() {
job = CoroutineScope(Dispatchers.IO).launch {
val myDataList = mainRepository.myData()
withContext(Dispatchers.Main) {
myDataResponse.postValue(handleMyDataResponse(myDataList))
}
}
}
private fun handleMyDataResponse(myDataResponse: Response<DataResponse>): HandleResource<DataResponse>? {
if (myDataResponse.isSuccessful) {
myDataResponse.body()?.let { myDataData ->
return HandleResource.Success(myDataData)
}
}
return HandleResource.Error(myDataResponse.message())
}
I need a solution while server give me error message I want to show same error message on my front side. How can I achieve this?
private fun handleMyDataResponse(myDataResponse: Response<DataResponse>): HandleResource<DataResponse>? {
myDataResponse.body()?.let { myDataData ->
if (myDataResponse.code() == 200) {
return HandleResource.Success(myDataData )
} else {
val rawResponse = myDataData.string()
return HandleResource.Error(getErrorMessage(rawResponse))
}
}
}
fun getErrorMessage(raw: String): String{
val object = JSONObject(raw);
return object.getString("message");
}
The body of the response (be it success or failure) is response.body(). And if you want to get it as a String, then call response.body().string(). Since you want to read message object from the response you need to convert it into Json.
If you are a following MVVM pattern then I suggest to create a sealed class for the API calls.
To handle api success and failure or network issue. Resource class is going to be generic because it will handle all kind of api response
sealed class Resource<out T> {
data class Success<out T>(val value: T): Resource<T>()
data class Failure(
val isNetworkErro: Boolean?,
val errorCode: Int?,
val errorBody: ResponseBody?
): Resource<Nothing>()
}
on the base repository while calling the API, you can return the resource whether it is success or failure.
abstract class BaseRepository {
suspend fun <T> safeApiCall(
apiCall: suspend () -> T
): Resource<T>{
return withContext(Dispatchers.IO){
try {
Resource.Success(apiCall.invoke())
} catch (throwable: Throwable){
when (throwable){
is HttpException -> {
Resource.Failure(false,throwable.code(), throwable.response()?.errorBody())
}
else ->{
Resource.Failure(true, null, null)
}
}
}
}
}
}
If you follow this pattern you'll be able to handle all the failure and success response, I hope this will help.

Handling error and success response from API using Android Clean Architechture

I'm trying to implement Clean Architecture in my app. My API sends response in success and failure cases as:
{
"data": [],
"statusCode": 200,
"statusMessage": "success",
"success": true
}
OR
{
"statusCode": xxx,
"statusMessage": "Invalid Details",
"success": false
}
In the previous approach I used Retrofit and BaseResponse POJO class to handle the data scenarios, but in my current app I'm using Clean Architecture with RxJava & Retrofit.
I will get one of Observable, Flowable or Single as response but with BaseResponse as type.
data class BaseResponse<T>(
#SerializedName("status") val status: Boolean,
#SerializedName("statuscode") val statusCode: Int? = null,
#SerializedName("message") val message: String? = null,
#SerializedName("data") val data: T? = null
)
I'm checking Rx Error in Domain using Rx with custom RESULT as success or failure for all:
fun <T> Single<T>.toResult(): Single<Result<T>> = this
.map { Result.Success(it) as Result<T> }
.onErrorReturn {
Result.Failure(
if (it is CompositeException) it.exceptions.last().toView()
else it.toView()
)
}
but for handling Dynamic Response I'm not able wrap them.
I tried the approach mentioned in
How to wrap API responses to handle success and error based on Clean Architecture? and couple of others, but none of them worked out.
How to wrap the items either in usecases / repository or in Data layer and pass them to presentation like data as Result.success or statusMessage as Result.Error
if data in your JSON response is null , you can just ignore the <T> in Base Response and can use Either<A, B> custom class in case of Success or failure.
U can create Custom Either as mentioned in here
so in your Repository code looks like
fun getMyList(): Single<Either<BaseModel, CustomModel?>> =
apiClient.getList()
.map {
if (it.status.equals("success")) {
Either.Success(it.data?.toEntity())
} else {
Either.Failure(it.toEntity())
}
}
fun <T> BaseResponse<T>.toEntity() = BaseModel(
status = status,
statusCode = statusCode,
message = message
)
Same goes with your CustomResponse.toEntity to CustomModel , However you are doing Single<T>.toResult() so in your Presentation layer , you can check like
.subscribe({ result ->
when (result) {
is Result.Success -> result.data.successOrFailure(::hanldeError, ::handleSuccess)
is Result.Failure -> {}
}
},{})
and do your operations in hanldeError() and handleSuccess() respectively

How to gey body() response using RxJava in Android

I am new to Kotlin and I am making a method that makes a call to an interface of Endpoints and uses one of the methods present there. I am using Observable<> instead of Call<> into the response. I wanted to know how to obtain the response body() in the "result" above. This is my method
private fun refreshUser(userLogin: String) {
executor.execute {
// Check if user was fetched recently
val userExists = userDao.hasUser(userLogin, getMaxRefreshTime(Date())) != null
// If user have to be updated
if (!userExists) {
disposable = endpoints.getUser(userLogin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> /*Get the response body() HERE*/},
{ error -> Log.e("ERROR", error.message) }
)
}
}
}
It all depends on how you have defined the Retrofit interface. In order to get the Response you need to return something from the interface that looks like:
fun getUsers() : Observable<Response<User>>
Then inside { result -> /*Get the response body() HERE*/}, you will get something of the form Response<User>, which has the response's body.
Also to note, you do not need to enclosing executor if you leverage Room for the dao interactions; it has RxJava support. You can use RxJava operators to combine the dao lookup with the server call.
See this tutorial
https://medium.freecodecamp.org/rxandroid-and-kotlin-part-1-f0382dc26ed8
//Kotlin
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
//each subscription is going to be on a new thread.
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
//Now our subscriber!
.subscribe(object:Subscriber<String>(){
override fun onCompleted() {
//Completed
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: String?) {
Log.e("Output",t);
}
})
if you wanna use retrofit 2 and rxjava 2
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
interface WikiApiService {
#GET("api.php")
fun hitCountCheck(#Query("action") action: String,
#Query("format") format: String,
#Query("list") list: String,
#Query("srsearch") srsearch: String):
Observable<Model.Result>
}
Observable is the class response.
private fun beginSearch(srsearch: String) {
disposable =
wikiApiServe.hitCountCheck("query", "json", "search", srsearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> showResult(result.query.searchinfo.totalhits) },
{ error -> showError(error.message) }
)
}
If, as you mentioned to #Emmanuel, the return type of your getUser() method is Observable<Response<User>> then calling result.body() will yield the resulting User.
{ result ->
val user: User = result.body()
}
If however, you are looking for the the raw response, you can instead call result.raw().body(); which will return an okhttp3.ResponseBody type.
{ result ->
val body: ResponseBody = result.raw().body()
val text: String = body.string()
}

How to create Generic Response handler for error and validate response using retrofit,rxjava and dagger

I'm trying to create generic architecture to consume complex json structure as follows:
Json Format
{
"type": "success",
"code": "s-groups-0006",
"description": "Index List successfully",
"result": {
"asOnDate": 1505457095278,
"indexList": [
{
"change": "22.35",
"changePercent": "0.27",
"isDefault": true,
"isEditable": false
}
]
}
}
Dagger Format
#Singleton
fun provideGson(): Gson =
GsonBuilder()
.setLenient()
// .registerTypeAdapter(BaseResponse::class.java, RestDeserializer<BaseResponse<T>>())
.create()
Rest Deseralizers
class RestDeserializer<T> : JsonDeserializer<T> {
#Throws(JsonParseException::class)
override fun deserialize(je: JsonElement, type: Type, jdc: JsonDeserializationContext): T? {
val content = je.asJsonObject
// Deserialize it. You use a new instance of Gson to avoid infinite recursion
// to this deserializer
return Gson().fromJson<T>(content, type)
}
}
Error callback
abstract class ErrorCallBack<T : BaseResponse<T>> : DisposableObserver<T>() {
protected abstract fun onSuccess(t: T)
override fun onNext(t: T) {
//You can return StatusCodes of different cases from your API and handle it here. I usually include these cases on BaseResponse and iherit it from every Response
onSuccess(t)
}
override fun onError(e: Throwable) {
when (e) {
is HttpException -> {
val responseBody = (e).response().errorBody()
responseBody?.let {
L.e("Error in call htttp exception")
}
}
is SocketTimeoutException -> {
// todo
L.e("Error in Socket time out")
}
is IOException -> {
// todo
L.e("Error in IO Exception")
}
else -> {
e.message?.let {
// todo
}
}
}
}
override fun onComplete() {
}
private fun getErrorMessage(responseBody: ResponseBody): String {
return try {
val jsonObject = JSONObject(responseBody.string())
jsonObject.getString("message")
} catch (e: Exception) {
e.message!!
}
}
}
Repository
override fun getValidateUser(validateUser: ValidateUser): LiveData<ValidateUserResponse> {
val mutableLiveData = MutableLiveData<ValidateUserResponse>()
remoteServices.requestValidateUser(validateUser)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : ErrorCallBack<BaseResponse<ValidateUserResponse>>() {
override fun onSuccess(t: BaseResponse<ValidateUserResponse>) {
if (t.type == CommonContents.SUCCESS) {
L.d("Success in validate user")
mutableLiveData.value = transform(t)
} else {
L.e("Validate User Error")
}
}
})
return mutableLiveData
}
Data Class
data class BaseResponse<out T>(
#SerializedName(CommonContents.TYPE) val type: String,
#SerializedName(CommonContents.CODE) val Code: String,
#SerializedName(CommonContents.DESCRIPTION) val Description: String,
#SerializedName(CommonContents.RESULT)val result: T? = null)
These are my structures and I try to make a generic structure but am facing an issue when invoking the Error callback.
Please guide me how to achieve this. Can i call generic method inside generic Response?
.subscribeWith(object : ErrorCallBack<BaseResponse<ValidateUserResponse>>() {
Guide To Achieving Working Code
This is my guide to making some code work. It is based on the principles of Test Driven Development.
Set up your unit test environment in your IDE. Personally, I have been using JUnit 4 with Eclipse, but you may prefer JUnit 5 with JetBrains' IDE.
Write a unit test for your ErrorCallback class constructor. Make it pass. Next, write unit tests for each of the methods, to see that it behaves the way that you expect.
Write unit tests where your test fixture is a PublishSubject<BaseResponse<Integer>> for some number of different cases: normal data, sequence of normal data, normal data followed by error, normal data followed by completion.
From there, add some more tests so that you can test the entire observable chain.

Retrofit and Moshi: how to handle com.squareup.moshi.JsonDataException

This scene takes place in an Android app using Retrofit2 and Moshi for JSON deserialization.
In a case where you don't have control over the server's implementation, and this said server have an inconsistent behavior in how it answers requests (also know as "a bad case"):
Is there a way to handle com.squareup.moshi.JsonDataException without crashing?
For example you expected a JSONArray, and here comes a JSONObject. Crash. Is there another way to handle this than having the app crashing?
Also in the case the server's implementation is updated, wouldn't it be better to display an error message to the user, instead of having it to crash / be totally out of service, even for one wrong request?
Make the call with Retrofit and use try and catch to handle exceptions, something similar to:
class NetworkCardDataSource(
private val networkApi: NetworkCardAPI,
private val mapper: CardResponseMapper,
private val networkExceptionMapper: RetrofitExceptionMapper,
private val parserExceptionMapper: MoshiExceptionMapper
) : RemoteCardDataSource {
override suspend fun getCard(id: String): Outcome<Card, Throwable> = withContext(Dispatchers.IO) {
val response: Response<CardResponseJson>
return#withContext try {
response = networkApi.getCard(id)
handleResponse(
response,
data = response.body(),
transform = { mapper.mapFromRemote(it.card) }
)
} catch (e: JsonDataException) {
// Moshi parsing error
Outcome.Failure(parserExceptionMapper.getException(e))
} catch (e: Exception) {
// Retrofit error
Outcome.Failure(networkExceptionMapper.getException(e))
}
}
private fun <Json, D, L> handleResponse(response: Response<Json>, data: D?, transform: (D) -> L): Outcome<L, Throwable> {
return if (response.isSuccessful) {
data?.let {
Outcome.Success(transform(it))
} ?: Outcome.Failure(RuntimeException("JSON cannot be deserialized"))
} else {
Outcome.Failure(
HTTPException(
response.message(),
Exception(response.raw().message),
response.code(),
response.body().toString()
)
)
}
}
}
where:
networkApi is your Retrofit object,
mapper is a class for mapping the received object to another one used in your app (if needed),
networkExceptionMapper and parserExceptionMapper map Retrofit and Moshi exceptions, respectively, to your own exceptions so that Retrofit and Moshi exceptions do not spread all over your app (if needed),
Outcome is just a iOS Result enum copy to return either a Success or a Failure result but not both,
HTTPException is a custom Runtime exception to return unsuccessful request.
This a snippet from a clean architecture example project.

Categories

Resources