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
Related
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.
Occasionally APIs can decide to do a ui job before finishing a network call (e.g. showing a webview dialog). Those flags are inside response headers and the value is usually the data, like the url of that webview.
This is the interceptor
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(request)
return when (response.code()) {
200 -> {
response.headers().forEach { /* do something in ui layer if needed */}
response;
}
}
}
Avoiding EventBus is intended, so I would add a meta data object to the response and then pass it to the repository and parse it in viewmodel, and produce the events for Activities and Fragments, but is not known to us when this interception is fired so one has to check for all types of events in all of the viewmodels for every api call.
The solution is to add an InterceptionMetaData to the api result.
data class InterceptionMetaData(
val key: String,
val data: String
)
Add this data class to the response (usually the success type, or we can add another Intercepted response type)
when (this) {
is Success -> success?.invoke(this.data, metaData)
is Error -> error?.invoke(this.error)
is Loading -> loading?.invoke()
is Intercepted -> onIntercepted?.invoke(this.data, interceptionMetaDataList)
}
in our interceptor we check for response headers (I have used all headers here for demonstration)
val interceptionMetaDataList: List<InterceptionMetaData> = response.headers().run {
names().map { key ->
get(key)?.let { InterceptionMetaData(key, it) }
}
}.filterNotNull()
and in our repositories we can handle the request and return the actual type to viewmodels.
In viewmodel, another response callback handler can be added (or it can be handled in the already used success callback)
repository.getNewsList().collectResult(
onIntercepted = { data, interceptionList ->
Log.d(NewsViewModel::class.java.simpleName, "getNewsList: $interceptionList")
},
success = { data ->
hideProgress()
handleData(data)
},
error = { error ->
hideProgress()
showNoData(error)
},
loading = {
showProgress()
})
Usually we have base viewmodels, so we can have our BaseViewModel build an event and pass it to our BaseFragment/BaseActivity to handle our actual ui job (e.g starting a dialog). That functionality is used where I have put Log.d.
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...
}
}
I was looking other questions but this one is using a different method.
I’m using MVVM in my Android app.
Actually this is the way that I’m getting data from my server:
Inject dataManager into viewModel.
viewModel calls dataManager->fetchUsers
fetchUsers make request to server and return an Observable of Several which in this case should be Several but it’s generic.
viewModel subscribe to this request and expect a Several.
At this point everything works besides Several doesn’t have a list of User. This several have a list of LinkedTreeMap
I tried to change my dataManager to return a string then map the response in my viewModel but the thing with this is that I will have to do that in every request.
Also I tried to map the request in my dataManager but I got the same link tree map array.
The thing with TypeToken approach is that I have to map in my viewModel.
UPDATED
UsersViewModel
private fun fetchUsers() {
isLoading.set(false)
compositeDisposable += dataManager.GET<User>(classJava = User::class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( { response ->
isLoading.set(false)
// Here the response should be a several with a list of users.
}, { err ->
isLoading.set(false)
})
}
GET function:
override fun <T> GET(classJava: KClass<*>): Single<Several<T>> {
return Rx2AndroidNetworking.get("EndPoint")
.addHeaders("Authorization", apiHeader.protectedAPIHeader.accessToken!!)
.setOkHttpClient(APIClient.getUnsafeClient())
.build()
// Also I tested with
// .getObjectSingle(Several::class.java) // The thing is that I can't assign the type <T> with this approach.
.stringSingle
.map {
val fromJSON = fromJson<Several<T>>(it)
fromJSON
}
}
fromJson function:
inline fun <reified T> fromJson(json: String): T = Gson().fromJson(json, object: TypeToken<T>() {}.type)
Several.kt
data class Several<T>(val items: MutableList<T>)
If I change my GET function to return a String then in UsersViewModel I add a .map like this
private fun fetchUsers() {
isLoading.set(false)
compositeDisposable += dataManager.GET<User>(classJava = User::class)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(
val severalUsers = fromJson<Several<User>>(it)
severalUsers
)
.subscribe( { response ->
isLoading.set(false)
// usersLiveData.value = response
}, { err ->
Log.e("tag", "Ocurred some error")
isLoading.set(false)
})
}
Then I would have a list of Users as expected but What I dont want to do is map the response in UsersViewModel because I would have to do in every single request that expect a list of items.
Is there any way that I could get a several object without map in my viewmodel?
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.