Exception handling of network errors retrofit - android

I was wondering what is the best way to handle network errors in retrofit requests when using coroutines.
The classic way is handling exception at highest level, when a request is made:
try {
// retrofit request
} catch(e: NetworkException) {
// show some error message
}
I find this solution wrong and it adds a lot of boilerplate code, instead I went with creating an interceptor that returns a error response:
class ErrorResponse : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
return try {
chain.proceed(request)
} catch (e: Exception) {
Snackbar.make(
view,
context.resources.getText(R.string.network_error),
Snackbar.LENGTH_LONG
).show()
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(599)
.message(e.message!!)
.body(ResponseBody.create(null, e.message!!))
.build()
}
}
}
This solution is a little better, however I think that it can be improved.
So my question is: What is the correct way to handle the cases when user doesn't have internet connection, without a lot of boilerplate code (ideally with a global handler in case of connection errors) ?

Using Result to wrap my response
sealed class Result<out T : Any> {
data class Success<out T : Any>(val value: T) : Result<T>()
data class Failure(val errorHolder:ErrorHolder) : Result<Nothing>()}
ErrorHolder :
sealed class ErrorHolder(override val message):Throwable(message){
data class NetworkConnection(override val message: String) : ErrorHolder(message)
data class BadRequest(override val message: String) : ErrorHolder(message)
data class UnAuthorized(override val message: String) : ErrorHolder(message)
data class InternalServerError(override val message: String) :ErrorHolder(message)
data class ResourceNotFound(override val message: String) : ErrorHolder(message)
}
an extension to handle exeptions
suspend fun <T, R> Call<T>.awaitResult(map: (T) -> R): Result<R> = suspendCancellableCoroutine { continuation ->
try {
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, throwable: Throwable) {
errorHappened(throwable)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
try {
continuation.resume(Result.Success(map(response.body()!!)))
} catch (throwable: Throwable) {
errorHappened(throwable)
}
} else {
errorHappened(HttpException(response))
}
}
private fun errorHappened(throwable: Throwable) {
continuation.resume(Result.Failure(asNetworkException(throwable)))
}
})
} catch (throwable: Throwable) {
continuation.resume(Result.Failure(asNetworkException(throwable)))
}
continuation.invokeOnCancellation {
cancel()
}}
And this how I make the api call:
suspend fun fetchUsers(): Result<List<User>> {
return service.getUsers().awaitResult { usersResponseDto ->
usersResponseDto.toListOfUsers()
}
}
UPDATE:
Let's say you have an error body like below:
{
"error" : {
"status" : 502,
"message" : "Bad gateway."
}
}
First we should create an data class to model response body
data class HttpErrorEntity(
#SerializedName("message") val errorMessage: String,
#SerializedName("status") val errorCode: Int
)
and here is asNetworkException implementation :
private fun asNetworkException(ex: Throwable): ErrorHolder {
return when (ex) {
is IOException -> {
ErrorHolder.NetworkConnection(
"No Internet Connection"
)
}
is HttpException -> extractHttpExceptions(ex)
else -> ErrorHolder.UnExpected("Something went wrong...")
}
}
private fun extractHttpExceptions(ex: HttpException): ErrorHolder {
val body = ex.response()?.errorBody()
val gson = GsonBuilder().create()
val responseBody= gson.fromJson(body.toString(), JsonObject::class.java)
val errorEntity = gson.fromJson(responseBody, HttpErrorEntity::class.java)
return when (errorEntity.errorCode) {
ErrorCodes.BAD_REQUEST.code ->
ErrorHolder.BadRequest(errorEntity.errorMessage)
ErrorCodes.INTERNAL_SERVER.code ->
ErrorHolder.InternalServerError(errorEntity.errorMessage)
ErrorCodes.UNAUTHORIZED.code ->
ErrorHolder.UnAuthorized(errorEntity.errorMessage)
ErrorCodes.NOT_FOUND.code ->
ErrorHolder.ResourceNotFound(errorEntity.errorMessage)
else ->
ErrorHolder.Unknown(errorEntity.errorMessage)
}
}

By implementing Interceptor, you are in right way. But by a little change, you can this sample class:
class NetworkConnectionInterceptor(val context: Context) : Interceptor {
#Suppress("DEPRECATION")
private val isConnected: Boolean
get() {
var result = false
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cm?.run {
cm.getNetworkCapabilities(cm.activeNetwork)?.run {
result = when {
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
}
}
} else {
cm?.run {
cm.activeNetworkInfo?.run {
if (type == ConnectivityManager.TYPE_WIFI) {
result = true
} else if (type == ConnectivityManager.TYPE_MOBILE) {
result = true
}
}
}
}
return result
}
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
if (!isConnected) {
// Throwing your custom exception
// And handle it on onFailure
}
val builder = chain.request().newBuilder()
return chain.proceed(builder.build())
}
}
Then add it to your OkHttpClient.Builder():
.addInterceptor(NetworkConnectionInterceptor(context));
And in failure you can handle it in onFailure method like this:
override fun onFailure(call: Call<BaseModel>, t: Throwable) {
if (t is NoConnectivityException) {
// Handle it here :)
}
}

Related

Handling Api errors with wrong querys

When I enter the city name correctly everything goes fine but when the user enters the wrong city name it causes this error
{
"error": {
"code": 1006,
"message": "No matching locations found."}
}
How can I handle this error?
Api
interface Api{
#GET("forecast.json")
suspend fun getCurrentTemp(#Query("key")key : String, #Query("q")q: String,
#Query("days")days : Int): Response<Weatherapi>
companion object {
operator fun invoke(
):Api{
return Retrofit.Builder().baseUrl("https://api.weatherapi.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(Api::class.java)
}
}
}
Repository:
abstract class repositoryApi {
suspend fun <T : Any> CustomResponse(work: () ->Response <T>): T {
val response: Response<T> = work.invoke()
if (response.isSuccessful)
return response.body()!!
throw Exception(response.message())
}
}
handelRequst:
object handelRequst: repositoryApi() {
suspend fun <T:Any> Requst (response: Response<T>) = CustomResponse { response } }
handelCoroutins:
object handelCoroutins {
fun <T:Any> ThreadMain(work:suspend (() -> T) ,callback : ((T) -> Unit),ErrorMessage :
((String) -> Unit))=
CoroutineScope(Dispatchers.Main).launch {
try{
val data :T = CoroutineScope(Dispatchers.IO).async rt#{
return#rt work()
}.await()
callback(data)
}catch(e : IOException){
ErrorMessage.invoke("Error C")
}
}
}
viewModel:
class viewModelapi: ViewModel() {
val LivedataErrorhandel = MutableLiveData<String>()
var weather = MutableLiveData<Weatherapi>()
lateinit var job: Job
fun Gethome(key :String , q :String ,days :Int) {
try {
job = handelCoroutins.ThreadMain(
{
handelRequst.Requst(Api.invoke().getCurrentTemp(key ,q ,days))
},
{
weather.value = it
}, {
LivedataErrorhandel.value = it
}
)
} catch (e: IOException) {
LivedataErrorhandel.value = "Error C"
}
}
}
main Activity :
viewmodel.weather.observe(requireActivity(), Observer{
textViewtemp.text = it.current.temp_c.toString()
}
I'm not giving the full answer I just give you an idea of how you can handle this. Here's some code you might look at carefully I hope you can take this your way.
if (response.isSuccessful) {
return response.body()!!
} else {
//this is a json object that you should return for handle error
var error:JSONObject? = null
try {
//heres I convert error response to json object
error = JSONObject(response.errorBody()!!.charStream().readText())
//you may know how can you get this exception on your api implementation area
throw CustomException(error)
} catch (e: JSONException) {
throw Exception("Something is wrong !! ")
}
}
CustomException class
class CustomException(error:JsonObject):Exception()
heres how you should implement
try {
job = handelCoroutins.ThreadMain(
{
handelRequst.Requst(Api.invoke().getCurrentTemp(key ,q ,days))
},
{
weather.value = it
}, {
LivedataErrorhandel.value = it
}
)
} catch (e: IOException) {
LivedataErrorhandel.value = "Error C"
}catch(error:CustomException){
//heres you got the json object }

What is the simplest way to make a post request in Kotlin for Android app

The question about post requests in android has been asked before, but all the solutions I've tried have not worked properly. On top of that, a lot of them seem to be overly complicated as well. All I wish to do is make a post to a specific sight with a few body parameters. Is there any simple way to do that?
Let me explain my request calling structure using Retrofit.
build.gradle(app)
// Retrofit + GSON
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
ApiClient.kt
object ApiClient {
private const val baseUrl = ApiInterface.BASE_URL
private var retrofit: Retrofit? = null
private val dispatcher = Dispatcher()
fun getClient(): Retrofit? {
val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
logging.level = HttpLoggingInterceptor.Level.BODY
else
logging.level = HttpLoggingInterceptor.Level.NONE
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(OkHttpClient().newBuilder().readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS).retryOnConnectionFailure(false)
.dispatcher(
dispatcher
).addInterceptor(Interceptor { chain: Interceptor.Chain? ->
val newRequest = chain?.request()!!.newBuilder()
return#Interceptor chain.proceed(newRequest.build())
}).addInterceptor(logging).build()
)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
ApiClient will be used to initialize Retrofit singleton object, also initialize logging interceptors so you can keep track of the requests and responses in the logcat by using the keyword 'okhttp'.
SingleEnqueueCall.kt
object SingleEnqueueCall {
var retryCount = 0
lateinit var snackbar: Snackbar
fun <T> callRetrofit(
activity: Activity,
call: Call<T>,
apiName: String,
isLoaderShown: Boolean,
apiListener: IGenericCallBack
) {
snackbar = Snackbar.make(
activity.findViewById(android.R.id.content),
Constants.CONST_NO_INTERNET_CONNECTION, Snackbar.LENGTH_INDEFINITE
)
if (isLoaderShown)
activity.showAppLoader()
snackbar.dismiss()
call.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
hideAppLoader()
if (response.isSuccessful) {
retryCount = 0
apiListener.success(apiName, response.body())
} else {
when {
response.errorBody() != null -> try {
val json = JSONObject(response.errorBody()!!.string())
Log.e("TEGD", "JSON==> " + response.errorBody())
Log.e("TEGD", "Response Code==> " + response.code())
val error = json.get("message") as String
apiListener.failure(apiName, error)
} catch (e: Exception) {
e.printStackTrace()
Log.e("TGED", "JSON==> " + e.message)
Log.e("TGED", "Response Code==> " + response.code())
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
}
else -> {
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
return
}
}
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
hideAppLoader()
val callBack = this
if (t.message != "Canceled") {
Log.e("TGED", "Fail==> " + t.localizedMessage)
if (t is UnknownHostException || t is IOException) {
snackbar.setAction("Retry") {
snackbar.dismiss()
enqueueWithRetry(activity, call, callBack, isLoaderShown)
}
snackbar.show()
apiListener.failure(apiName, Constants.CONST_NO_INTERNET_CONNECTION)
} else {
retryCount = 0
apiListener.failure(apiName, t.toString())
}
} else {
retryCount = 0
}
}
})
}
fun <T> enqueueWithRetry(
activity: Activity,
call: Call<T>,
callback: Callback<T>,
isLoaderShown: Boolean
) {
activity.showAppLoader()
call.clone().enqueue(callback)
}
}
SingleEnqueueCall will be used for calling the retrofit, it is quite versatile, written with onFailure() functions and by passing Call to it, we can call an API along with ApiName parameter so this function can be used for any possible calls and by ApiName, we can distinguish in the response that which API the result came from.
Constants.kt
object Constants {
const val CONST_NO_INTERNET_CONNECTION = "Please check your internet
connection"
const val CONST_SERVER_NOT_RESPONDING = "Server not responding!
Please try again later"
const val USER_REGISTER = "/api/User/register"
}
ApiInterface.kt
interface ApiInterface {
companion object {
const val BASE_URL = "URL_LINK"
}
#POST(Constants.USER_REGISTER)
fun userRegister(#Body userRegisterRequest: UserRegisterRequest):
Call<UserRegisterResponse>
}
UserRegisterRequest.kt
data class UserRegisterRequest(
val Email: String,
val Password: String
)
UserRegisterResponse.kt
data class UserRegisterResponse(
val Message: String,
val Code: Int
)
IGenericCallBack.kt
interface IGenericCallBack {
fun success(apiName: String, response: Any?)
fun failure(apiName: String, message: String?)
}
MyApplication.kt
class MyApplication : Application() {
companion object {
lateinit var apiService: ApiInterface
}
override fun onCreate() {
super.onCreate()
apiService = ApiClient.getClient()!!.create(ApiInterface::class.java)
}
}
MyApplication is the application class to initialize Retrofit at the launch of the app.
AndroidManifest.xml
android:name=".MyApplication"
You have to write above tag in AndroidManifest inside Application tag.
MainActivity.kt
class MainActivity : AppCompatActivity(), IGenericCallBack {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val call = MyApplication.apiService.userRegister(UserRegisterRequest(email, password))
SingleEnqueueCall.callRetrofit(this, call, Constants.USER_REGISTER, true, this)
}
override fun success(apiName: String, response: Any?) {
val model = response as UserRegisterResponse
}
override fun failure(apiName: String, message: String?) {
if (message != null) {
showToastMessage(message)
}
}
}
Firstly, we create a call object by using the API defined in ApiInterface and passing the parameters (if any). Then using SingleEnqueueCall, we pass the call to the retrofit along with ApiName and the interface listener IGenericCallBack by using this. Remember to implement it to respective activity or fragment as above.
Secondly, you will have the response of the API whether in success() or failure() function overriden by IGenericCallBack
P.S: You can differentiate which API got the response by using the ApiName parameter inside success() function.
override fun success(apiName: String, response: Any?) {
when(ApiName) {
Constants.USER_REGISTER -> {
val model = response as UserRegisterResponse
}
}
}
The whole concept is to focus on reusability, now every API call has to create a call variable by using the API's inside ApiInterface then call that API by SingleEnqueueCall and get the response inside success() or failure() functions.

error handling with Retrofit2 using Kotlin coroutines

I understand how to handle errors when not using coroutines:
#GET("user/{user}")
fun getHomeData(#Path("user") user: String?): Call<HomeDataBody>
fun getHomeData(id:String, callback: (Boolean, String?) -> Unit)
{
val call = service.getHomeData(id)
call.enqueue( object : Callback<HomeDataBody> {
override fun onResponse(call: Call<HomeDataBody>, response: Response<HomeDataBody>)
{
if (response.isSuccessful)
{
dataMgr.homeData = response.body()!!.user
callback(true, null)
}
else
{
callback(false, response.message())
}
}
override fun onFailure(call: Call<HomeDataBody>, t: Throwable)
{
callback(false, t.message)
}
})
}
But I cannot for the life of me figure out how to do this with coroutines, this is what I have for a coroutine that does not return errors:
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): HomeData
suspend fun getHomeDataCoroutine(id:String) : Pair<Boolean, String>
{
val data = service.getHomeDataCoroutine(id)
if(data != null)
{
dataMgr.homeData = data
}
else
{
return Pair(false, "how do i get the error message??")
}
}
I also attempted this, but when I try to call service.getHomeDataCoroutine I get this error:
java.lang.IllegalArgumentException: Unable to create call adapter for class java.lang.Object
for method RiseServiceRetro.getHomeDataCoroutine
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): Deferred<HomeDataBody>?
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()
suspend fun getHomeDataCoroutine(id:String): Result<HomeDataBody>
{
try {
val response = service.getHomeDataCoroutine(id)!!.await()
return Success(response)
} catch (e: Exception) {
return Error(e)
}
}
To handle errors when calling suspend function of Retrofit service wrap it in try-catch block:
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): HomeDataBody
suspend fun getHomeDataCoroutine(id:String): Pair<Boolean, String> {
return try {
val data = service.getHomeDataCoroutine(id)
dataMgr.homeData = data
Pair(true, "")
} catch(e: Throwable) {
Pair(false, e.message ?: "error")
}
}

Get a simple string from an API

I would like to get a simple string from an API.
Usually, i can get everything I want from an API with the following functions :
class OrderRepositoryImpl(val orderService: OrderService) : OrderRepository {
override fun getPaymentMethods(id: String, success: (List<PaymentMode>) -> Unit, failure: (Throwable) -> Unit): Subscription {
return orderService.getPaymentMethods(id)
.subscribeOn(Schedulers.io())
.map { it.entrySet() }
.map { it.map { it.value }.map {it.asJsonObject } }
.map { it.map { PaymentMode().apply { loadFromJson(it) } } }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it) }, { failure.invoke(it) })
}
}
And in OrderService :
#GET("api/order/payment/modes/list/{id}")
fun getPaymentMethods(#Path("id") id: String): Observable<JsonObject>
This works perfectly, on an API with regular Json Objects.
But today, I have a problem : I have an API with a unique string, like this :
"validated"
or :
"draft"
So I made the followin function (in OrderRepositoryImpl class) :
override fun getOrderStatus(id: String, success: (String) -> Unit, failure: (Throwable) -> Unit) =
orderService.getOrderStatus(id)
.subscribeOn(Schedulers.io())
.map { it }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it.toString()) }, { failure.invoke(it) } )
And in Order Service :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonObject>
I call this method like this :
fun getOrderStatus(id : Long) {
orderRepository.getOrderStatus(id.toString(), {
println("SUCCESS !")
println("STATUS == $it")
}, {
println("FAILURE...")
})
}
But I don't get anything from "success.invoke" line. When I call this method in my code, i always have "FAILURE" in my logs... Even if one of the logs lines is :
D/OkHttp: "validated"
which is exactly what I want to see in case of success.
I know it is pretty strange I can get and parse json objects and not a string, but I kind of learned on the job...
How can I get a simple string from an API ?
Okay, I am SO ashamed.
So, I printed the error in my "FAILURE..." section, and I got something like :
got jsonPrimitive but expected jsonObject
As the API was just returning me a string, and NOT an object, it is call a Json Primitive.
So I just changed the return of my functions in OrderService :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonPrimitive>
Thanks for your helpful comments.
interface ServiceInterFace {
#POST("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Call<String>
}
// calling from your main class
val id: String? = null
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl("----Your link here-----")
.build()
val scalarService = retrofit.create(ServiceInterFace::class.java!!)
val stringCall = scalarService.getOrderStatus(id)
stringCall.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.isSuccessful) {
val responseString = response.body()//get response here
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
Toast.makeText(this#Main2Activity, "Failed to connect server",
Toast.LENGTH_SHORT).show()
}
})
//import
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'

Return custom object from onError() of Rx java Android instead of throw-able object

I am new to RX Java. While implementing Rx java with Retrofit i found i am getting throw-able object in my doOnError(){}
But what i want my doOnError() of RX Java should return ErrorBase() -> that is my custom class. instead of throwable.
it will help me to handle error at central. i will pass my throw-able object to my ErrorBase class where i have handled custom messages.
Below is doOnError(). Where i want to return ErrorBase object
apiInterface.getLoginDetails(auth)
.doOnNext {
//LoginResponse
}
doOnError{
return ErrorBase(throwable)
}
Code of other classes.
Api Interface class
interface ApiInterface {
#POST("login")
fun getLoginDetails(#Header(Constants.AUTHORIZATION) auth: String): Observable<LoginResponseModel>
}
LoginRepository
class LoginRepository #Inject constructor(private val apiInterface: ApiInterface,
val utils: Utils) {
fun getLoginDetails(auth: String): Observable<LoginResponseModel> {
return apiInterface.getLoginDetails(auth)
.doOnNext {
}
.doOnError {
//Right now having throw-able object
}
}
}
ErrorBase
class ErrorBase(private val throwable: Throwable) {
private var message: String?
private var statusCode: Int
init {
statusCode = getStatusCode()
message = getMessage()
}
private fun getStatusCode(): Int {
if (throwable is HttpException) {
val exception = throwable
return exception.code()
}
return -1
}
private fun getMessage() =
when (throwable) {
is IOException -> "Something Went Wrong"
is UnknownHostException -> "No internet connectivity"
is SocketTimeoutException -> "Slow Internet connectivity"
else -> throwable.message
}
}
LoginvViewModel
class LoginViewModel #Inject constructor(
private val loginRepository: LoginRepository) : ViewModel() {
private val TAG = this.javaClass.name
private var loginResult: MutableLiveData<LoginResponseModel> = MutableLiveData()
private var loginError: MutableLiveData<String> = MutableLiveData()
private var loginLoader: MutableLiveData<Boolean> = MutableLiveData()
private lateinit var disposableObserver: DisposableObserver<LoginResponseModel>
fun loginResult(): LiveData<LoginResponseModel> {
return loginResult
}
fun loginError(): LiveData<String> {
return loginError
}
fun loginLoader(): LiveData<Boolean> {
return loginLoader
}
private fun getLoginData(auth: String) {
loginLoader.postValue(true)
initLoginObserver()
loginRepository.getLoginDetails(auth)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.debounce(400, MILLISECONDS)
.subscribe(disposableObserver)
}
private fun initLoginObserver() {
disposableObserver = object : DisposableObserver<LoginResponseModel>() {
override fun onComplete() {
}
override fun onNext(loginDetails: LoginResponseModel) {
loginResult.postValue(loginDetails)
loginLoader.postValue(false)
}
override fun onError(e: Throwable) {
loginError.postValue(e.message)
loginLoader.postValue(false)
}
}
}
fun disposeElements() {
if (null != disposableObserver && !disposableObserver.isDisposed) disposableObserver.dispose()
}
fun loginClicked() {
getLoginData("auth")
}}
Firstly, doOnError isn't aimed to transform/return some data, but helps to handle side-effects like logging.
Second thing, ErrorBase doesn't fit well together with LoginResponseModel cause they don't have any common parent.
Thus, I suggest you following solution:
Create one base class for your response:
sealed class LoginResponse {
class Result( ..your data here.. ) : LoginResponse()
class Error( ... ) : LoginResponse()
}
Make function return LoginResponse and do following changes:
fun getLoginDetails(auth: String): Observable<LoginResponse> {
return apiInterface.getLoginDetails(auth)
.map { data -> LoginResponse.Result(data) }
.onErrorReturn { throwable -> LoginResponse.Error(throwable) }
}
Now both results have one common parent and you can use getLoginDetails in the following way:
fun doRequest() {
loginRepository.getLoginDetails(auth)
.subscribe { result ->
when (result) {
is LoginResponse.Result -> //do something with result
is LoginResponse.Error -> //do something with error
}
}
}
Some explanation.
onErrorReturn does exactly what you need - returns your custom value in case if error occurs
If you don't add LoginResponse you have to make Observable<Any> which is loosely typed and doesn't really well describes your interface.
Making LoginResponse sealed allows to check only 2 cases whether emitted data is Result or Error. Otherwise Kotlin compiler forces you to add additional else branch
Update In case if you need to do same thing in multiple places you can go with this:
sealed class Response<T> {
data class Result<T>(val result: T) : Response<T>()
data class Error<T>(val throwable: Throwable) : Response<T>()
}
fun getLoginDetails(auth: String): Observable<Response<LoginResponseModel>> {
return apiInterface.getLoginDetails(auth)
.map<Response<LoginResponseModel>> { data -> Response.Result(data) }
.onErrorReturn { throwable -> LoginResponse.Error(throwable) }
}
..and somewhere in your code..
fun handleResponse(response: Response<LoginData>) {
when (response) {
is Response.Result -> response.result
is Response.Error -> response.throwable
}
}

Categories

Resources