Tarun
Tarun

Reputation: 1242

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")
}}

Upvotes: 4

Views: 3316

Answers (1)

hluhovskyi
hluhovskyi

Reputation: 10106

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:

  1. Create one base class for your response:

    sealed class LoginResponse {
    
        class Result( ..your data here.. ) : LoginResponse()
    
        class Error( ... ) : LoginResponse()
    }
    
  2. 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) }
    }
    
  3. 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
    }
}

Upvotes: 4

Related Questions